001package votorola.s.wic.count; // Copyright 2009-2013, Michael Allan.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE.
002
003import java.util.*;
004import votorola.a.count.*;
005import votorola.g.lang.*;
006import votorola.g.util.*;
007
008import static votorola.a.count.XCastRelation.CO_VOTER;
009import static votorola.a.count.XCastRelation.VOTER;
010
011
012/** A tier in a votespace page.  There are three types of tier: upstream, base, and
013  * orphan.  All are comprised of count nodes.  In an upstream tier, the nodes represent
014  * co-voters who are voting for the same candidate.  In a base tier, the nodes represent
015  * base candidates who are voting for nobody, or (in the case of cyclers) for themselves.
016  * Finally, an orphan tier represents a single non-participant, one who is neither a
017  * voter nor a candidate.
018  *
019  * <p>The rows of the tier are indexed from zero (top), and ordered as
020  * follows:</p><ul>
021  *
022  *     <li>{@linkplain #properRow properRow} (first and subsequent)</li>
023  *     <li>{@linkplain #otherNodesRow otherNodesRow} (if any)</li>
024  *     <li>{@linkplain #pathRow pathRow} (if not a proper node)</li>
025  *     <li>{@linkplain #crosspathRow crosspathRow} (if not a proper node)</li>
026  *     <li>{@linkplain #sumRow sumRow}</li>
027  *     <li>{@linkplain #footnoteRow footnoteRow} (first and subsequent)</li>
028  *
029  * </ul>
030  */
031final @ThreadRestricted("wicket") class Tier
032{
033
034    /** Constructs an orphan tier.
035      */
036    Tier( CountNodeW _pathNode, CountNodeW node )
037    {
038        this( _pathNode, null, new ArrayListU<CountNodeW>(new CountNodeW[] { node }),
039          null, null );
040    }
041
042
043
044    /** Constructs a tier from an ArrayList.
045      *
046      *     @param candidateNode the count node of the candidate in the downstream tier,
047      *       or null if this is a base or orphan tier.
048      */
049    Tier( CountNodeW pathNode, CountNodeW crosspathNode,
050      ArrayList<CountNodeW> properNodesList, CountNodeW candidateNode, Count count )
051    {
052        this( pathNode, crosspathNode, new ArrayListU<CountNodeW>(
053            properNodesList.toArray( new CountNodeW[properNodesList.size()] )),
054          candidateNode, count );
055    }
056
057
058
059    /** @param count the count, or null if this is an orphan tier.
060      */
061    private Tier( CountNodeW _pathNode, CountNodeW _crosspathNode,
062      ArrayListU<CountNodeW> _properNodesList, final CountNodeW candidateNode, final Count count )
063    {
064        pathNode = _pathNode;
065        crosspathNode = _crosspathNode;
066        properNodesList = _properNodesList;
067
068        int r = 0;
069        properRow = r; r += properNodesList.size();
070        final int pathRowProper;
071        final boolean pathNodeIsSeparate;
072        if( pathNode == null )
073        {
074            pathRowProper = -1;
075            pathNodeIsSeparate = false;
076        }
077        else
078        {
079            pathRowProper = properNodesList.indexOf( pathNode );
080            pathNodeIsSeparate = pathRowProper == -1;
081        }
082
083        final int crosspathRowProper;
084        final boolean crosspathNodeIsSeparate;
085        if( crosspathNode == null )
086        {
087            crosspathRowProper = -1;
088            crosspathNodeIsSeparate = false;
089        }
090        else if( crosspathNode.equals( pathNode ))
091        {
092            crosspathRowProper = pathRowProper;
093            crosspathNodeIsSeparate = false;
094        }
095        else
096        {
097            crosspathRowProper = properNodesList.indexOf( crosspathNode );
098            crosspathNodeIsSeparate = crosspathRowProper == -1;
099        }
100
101        {
102            CountCumulate c;
103            if( count == null ) c = null;
104            else
105            {
106                c = new CountCumulate();
107                if( candidateNode == null ) // base candidate tier
108                {
109                    c.outflowVolume = -1; // not calculated
110                    if( count.candidateCount() == 0 ) c.holdVolume = CountCumulate.NO_PARTICIPANTS;
111                    else if( count.baseCandidateCount() > properNodesList.size() )
112                    {
113                        c.holdVolume = count.holdVolume();
114                        if( c.holdVolume > 0 )
115                        {
116                            for( CountNodeW node: properNodesList ) c.holdVolume -= node.holdVolume();
117                            if( pathNodeIsSeparate ) c.holdVolume -= pathNode.holdVolume();
118                            if( crosspathNodeIsSeparate ) c.holdVolume -= crosspathNode.holdVolume();
119                        }
120                    }
121                    else c = null; // no other nodes
122                }
123                else // voter tier
124                {
125                    c.outflowVolume = candidateNode.receiveVolume();
126                    c.holdVolume = -1; // not calculated
127                    for( CountNodeW node: properNodesList )
128                    {
129                        c.outflowVolume -= node.castVolume();
130                        c.outflowVolume -= node.carryVolume();
131                    }
132                    if( pathNodeIsSeparate )
133                    {
134                        c.outflowVolume -= pathNode.castVolume();
135                        c.outflowVolume -= pathNode.carryVolume();
136                    }
137                    if( crosspathNodeIsSeparate )
138                    {
139                        c.outflowVolume -= crosspathNode.castVolume();
140                        c.outflowVolume -= crosspathNode.carryVolume();
141                    }
142                    if( c.outflowVolume == 0 ) c = null; // no other nodes
143                }
144            }
145            otherNodesCumulate = c;
146        }
147
148        otherNodesRow = otherNodesCumulate == null? -1: r++;
149        pathRow = pathNodeIsSeparate? r++: pathRowProper;
150        crosspathRow = crosspathNodeIsSeparate? r++: crosspathRowProper;
151        if( count == null ) sumRow = -1; // none for orphan tier
152        else
153        {
154            sumRow = r;
155            r += 2; /* 2 to match its double rowspan, which gives room for its borders
156              etc., so it doesn't affect the layout of other tiers */
157        }
158        footnoteRow = r;
159    }
160
161
162
163   // --------------------------------------------------------------------------------
164
165
166    /** The crosspath node, or null if there is none.  This corresponds to the node on the
167      * user's own vote path where it crosses the tier.  The crosspath node may be in or
168      * out of the proper node list, and equal or unequal to the path node.
169      */
170    final CountNodeW crosspathNode;
171
172
173
174    /** The row of the crosspath node, or -1 if there is no crosspath node.
175      */
176    final int crosspathRow;
177
178
179
180    final WP_Votespace.FootnoteBuilder footnoteBuilder = new WP_Votespace.FootnoteBuilder();
181
182
183
184    /** The first footnote row.
185      */
186    final int footnoteRow;
187
188
189
190    /** Returns the node for the specified row, which is either a proper node
191      * or a path node (or crosspath node) external to the proper list.
192      */
193    CountNodeW getNode( final int r )
194    {
195        final CountNodeW node;
196        if( r == crosspathRow ) node = crosspathNode;
197        else if( r == pathRow ) node = pathNode;
198        else node = properNodesList.get( r );
199        return node;
200    }
201
202
203
204    /** The row of the last node.  It is either a proper node, other node, or external
205      * path or crosspath node.
206      */
207    int lastNodeRow()
208    {
209        final int followingRow =  sumRow == -1? footnoteRow: sumRow;
210        return followingRow - 1;
211    }
212
213
214
215    /** The cumulative count of the other nodes, not individually shown in the
216      * view, or null if there are no other nodes.
217      */
218    final CountCumulate otherNodesCumulate;
219
220
221
222    /** The row of the other-nodes cumulate, or -1 if there are no other nodes.
223      */
224    final int otherNodesRow;
225
226
227
228    /** The path node, or null if there is none.  This path node may be in or out of the
229      * proper node list, and equal or unequal to the crosspath node.
230      */
231    final CountNodeW pathNode;
232
233
234
235    /** The row of the path node, or -1 if there is no path node.
236      */
237    final int pathRow;
238
239
240
241    /** The corresponding {@linkplain votorola.s.gwt.stage.vote.MajorV#place() place} in
242      * the vote track, or {@linkplain votorola.a.count.XCastRelation#UNKNOWN UNKNOWN} if
243      * there is none.
244      */
245    XCastRelation place = XCastRelation.UNKNOWN;
246
247
248        /** @param t the ordinal of this tier, counting from 0 (leftmost).
249          * @param tN the number of tiers.
250          */
251        void initPlace( final CountNodeW specificPathNode, final int t, final int tN )
252        {
253            if( tN == 1 ) place = CO_VOTER; // only one tier, they are base co-candidates
254            else if( specificPathNode.isCandidate() )
255            {
256                if( t == 0 ) place = VOTER; // first tier is voters
257                else if( t == 1 ) place = CO_VOTER; // second is co-voters or base co-candidates
258            }
259            else // no voters left of specific node, no voter board in vote track
260            {
261                if( !specificPathNode.isVoter() ) // non-participant, orphan
262                {
263                    if( t == 1 ) place = CO_VOTER; // second tier is co-voters or base co-candidates
264                }
265                else if( t == 0 ) place = CO_VOTER; // first tier is co-voters or base co-candidates
266            }
267        }
268
269
270
271    /** An unmodifiable list of the proper nodes in this tier.
272      */
273    final List<CountNodeW> properNodesList;
274
275
276
277    /** The row of the first proper node.
278      */
279    final int properRow;
280
281
282
283    /** The total number of rows.
284      */
285    int rowCount() { return footnoteRow + 1; }
286
287
288
289    /** The first of the two sum rows, or -1 if there are none.
290      */
291    final int sumRow;
292
293
294
295   // ====================================================================================
296
297
298    /** A total of counts across multiple count nodes.
299      */
300    static final class CountCumulate
301    {
302
303        /** The total of hold counts, or NO_PARTICIPANTS.  This is not calculated (is -1)
304          * for non-base candidates.
305          *
306          *     @see CountNodeW#holdVolume()
307          */
308        long holdVolume;
309
310
311
312        /** The negative value of holdVolume that signals a poll without participants.
313          */
314        static final long NO_PARTICIPANTS = -2L;
315
316
317        /** The total of combined cast and carry volume.  This is not calculated (is -1)
318          * for base candidates.
319          *
320          *     @see CountNodeW#castVolume()
321          *     @see CountNodeW#carryVolume()
322          */
323        long outflowVolume;
324
325    }
326
327
328}