Implement help command, add more tests, improve messages
[cacert-votebot.git] / src / main / java / org / cacert / votebot / shared / CAcertVoteMechanics.java
1 /*
2 * Copyright (c) 2015 Felix Doerre
3 * Copyright (c) 2015 Benny Baumann
4 * Copyright (c) 2016, 2018 Jan Dittberner
5 *
6 * This file is part of CAcert VoteBot.
7 *
8 * CAcert VoteBot is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation, either version 3 of the License, or (at your option)
11 * any later version.
12 *
13 * CAcert VoteBot is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 * more details.
17 *
18 * You should have received a copy of the GNU General Public License along with
19 * CAcert VoteBot. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 package org.cacert.votebot.shared;
23
24 import org.springframework.stereotype.Component;
25
26 import java.text.MessageFormat;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.ResourceBundle;
31 import java.util.regex.Pattern;
32
33 /**
34 * Represents the voting-automate for voting in IRC channels.
35 */
36 @Component
37 public class CAcertVoteMechanics {
38 private static final Pattern PROXY_RE = Pattern.compile("^\\s*proxy\\s.*");
39 private static final int VOTE_MESSAGE_PART_COUNT = 3;
40
41 private State state = State.IDLE;
42 private String topic;
43 private final Map<String, VoteType> votes = new HashMap<>();
44 private final ResourceBundle messages = ResourceBundle.getBundle("messages");
45
46 /**
47 * Voting state indicating whether a vote is currently running or not.
48 */
49 public enum State {
50 /**
51 * A vote is currently running.
52 */
53 RUNNING,
54 /**
55 * No vote is running.
56 */
57 IDLE
58 }
59
60 private String vote(final String voter, final String actor, final VoteType type) {
61 votes.put(voter, type);
62
63 if (voter.equals(actor)) {
64 return MessageFormat.format(messages.getString("count_vote"), actor, type);
65 } else {
66 return MessageFormat.format(messages.getString("count_proxy_vote"), actor, voter, type);
67 }
68 }
69
70 private String voteError(final String actor) {
71 return MessageFormat.format(messages.getString("vote_not_understood"), actor);
72 }
73
74 private String proxyVoteError(final String actor) {
75 return MessageFormat.format(messages.getString("invalid_proxy_vote"), actor);
76 }
77
78 /**
79 * Adds a vote to the current topic. This interprets proxies.
80 *
81 * @param actor the person that sent this vote
82 * @param txt the text that the person sent.
83 * @return A message to <code>actor</code> indicating the result of his action.
84 */
85 public synchronized String evaluateVote(final String actor, final String txt) {
86 if (state != State.RUNNING) {
87 return MessageFormat.format(messages.getString("no_vote_running"), actor);
88 }
89
90 final String voter;
91 final String value;
92
93 if (PROXY_RE.matcher(txt.toLowerCase()).matches()) {
94 String[] parts = txt.split("\\s+");
95 if (parts.length == VOTE_MESSAGE_PART_COUNT) {
96 voter = parts[1];
97 value = parts[2];
98 } else {
99 return proxyVoteError(actor);
100 }
101 } else {
102 voter = actor;
103 value = txt.trim();
104 }
105
106 try {
107 return vote(voter, actor, VoteType.evaluate(value));
108 } catch (IllegalArgumentException iae) {
109 return voteError(actor);
110 }
111 }
112
113 /**
114 * A new vote begins.
115 *
116 * @param topic the topic of the vote
117 * @return A response to <code>from</code> indicating success or failure.
118 */
119 public synchronized String callVote(final String topic) {
120 if (state != State.IDLE) {
121 return messages.getString("vote_running");
122 }
123
124 this.topic = topic;
125 votes.clear();
126
127 state = State.RUNNING;
128
129 return messages.getString("vote_started");
130 }
131
132 /**
133 * Ends a vote.
134 *
135 * @return An array of Strings containing result status messages.
136 */
137 public synchronized String[] closeVote() {
138 final int[] resultCounts = new int[VoteType.values().length];
139
140 for (final Entry<String, VoteType> voteEntry : votes.entrySet()) {
141 resultCounts[voteEntry.getValue().ordinal()]++;
142 }
143
144 final String[] results = new String[VoteType.values().length];
145
146 for (int i = 0; i < results.length; i++) {
147 results[i] = MessageFormat.format("{0}: {1}", VoteType.values()[i], resultCounts[i]);
148 }
149
150 votes.clear();
151 state = State.IDLE;
152 topic = "";
153
154 return results;
155 }
156
157 /**
158 * @return Topic of the current vote.
159 */
160 public String getTopic() {
161 return topic;
162 }
163
164 /**
165 * @return Voting state
166 */
167 public State getState() {
168 return state;
169 }
170
171 /**
172 * @return current vote results as string
173 */
174 public String getCurrentResult() {
175 return votes.toString();
176 }
177
178 }