/**
 * 
 */
package mathsquared.resultswizard2;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

/**
 * Handles communication between client and server, including slide selection.
 * 
 * @author MathSquared
 * 
 */
public class ProtocolSelector implements Selector {
    private int width;
    private int height;

    private LinkedHashMap<String, SlideList> slides; // stores the slides to display
    private String currentTag; // the current key in the map where to find the current slide
    private int currentIndex; // the index in the array given by currentTag

    public ProtocolSelector (int width, int height) {
        this.width = width;
        this.height = height;

        slides = new LinkedHashMap<String, SlideList>();
    }

    public Slide getCurrent () {
        if (currentTag == null) {
            return null;
        }
        if (!slides.containsKey(currentTag)) {
            return null;
        }
        if (slides.size() == 0) {
            return null; // this also checks that locateNextString won't IOOBE due to no entries in the key set
        }

        // Create duplicate field values to mess with (to canonicalize what we return)
        String tag = currentTag;
        int idx = currentIndex;

        // Ensure idx is in bounds
        if (idx < 0) {
            idx = 0;
        }
        if (idx > slides.get(tag).size()) {
            // Move to the next tag; continue while there are no slides in the current one
            String origTag = tag; // ensure we don't wrap around the keySet
            do {
                tag = locateNextString(tag);
            } while (slides.get(tag).size() == 0 && origTag != tag);
            idx = 0;
        }

        return slides.get(tag).get(idx);
    }

    public Command processMessage (Command msg) {
        switch (msg.getType()) {
        case POISON:
        case XMIT_ERROR_RESTART:
            currentTag = null;
            break;
        case ADD:
            slides.putAll(msg.getStringSlideListPayload());
            break;
        case REMOVE:
            slides.remove(msg.getStringPayload());
            break;
        case RETR_SLIDES:
            return new Command() {
                // Carry the slides along with the command
                private LinkedHashMap<String, SlideList> carriedSlides = new LinkedHashMap<String, SlideList>(slides);

                public Message getType () {
                    return Message.RESP_SLIDES;
                }

                public String getStringPayload () {
                    throw new UnsupportedOperationException("RESP_SLIDES does not carry a String payload");
                }

                public Map<String, SlideList> getStringSlideListPayload () {
                    return new LinkedHashMap<String, SlideList>(carriedSlides);
                }
            };
            // break; (unreachable)
        case TICKER:
            // TODO ticker not supported
            break;
        case RETR_TICKER:
            return new Command() {
                public Message getType () {
                    return Message.RESP_TICKER;
                }

                public String getStringPayload () {
                    return "";
                }

                public Map<String, SlideList> getStringSlideListPayload () {
                    throw new UnsupportedOperationException("RESP_TICKER does not carry a Map<String, SlideList> payload");
                }
            };
            // break; (unreachable)
        case RESP_SLIDES: // we don't expect these cases
        case RESP_TICKER:
            break;
        }

        // if we haven't already returned, nothing to send to the client
        return null;
    }

    private String locateNextString (String current) {
        LinkedList<String> keys = new LinkedList<String>();
        keys.addAll(slides.keySet());
        Iterator<String> iter = keys.iterator();

        // Iterate while we have entries until we skip over an entry equal to current
        while (iter.hasNext() && !iter.next().equals(current));

        // If we're out of entries, return the first one
        if (!iter.hasNext()) {
            return keys.get(0);
        }

        // Return the next string
        return iter.next();
    }
}

@@ -4330,11 +4330,624 @@
 );%0A    %7D
+%0A%0A    private void canonicalizeIndexing () %7B%0A        // Ensure currentIndex is in bounds%0A        if (currentIndex %3C 0) %7B%0A            currentIndex = 0;%0A        %7D%0A        if (currentIndex %3E slides.get(currentTag).size()) %7B%0A            // Move to the next tag; continue while there are no slides in the current one%0A            String origcurrentTag = currentTag; // ensure we don't wrap around the keySet%0A            do %7B%0A                currentTag = locateNextString(currentTag);%0A            %7D while (slides.get(currentTag).size() == 0 && origcurrentTag != currentTag);%0A            currentIndex = 0;%0A        %7D%0A    %7D
 %0A%7D%0A

Add canonicalizeIndexing