package edu.uw.zookeeper.client.cli;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Iterator;
import java.util.LinkedList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Lists;

import edu.uw.zookeeper.common.Pair;

public class TokenParser implements Function<CharSequence, Iterator<Pair<Integer, String>>> {

    public static TokenParser defaults() {
        return new TokenParser(SPACE);
    }
    
    protected static char QUOTE_CHAR = '"';

    protected static char ESCAPE_CHAR = '\\';

    protected static Character SPACE = Character.valueOf(' ');

    protected final Logger logger = LogManager.getLogger(getClass());
    protected final Pair<Character, CharMatcher> delimiter;
    
    public TokenParser(Character delimit) {
        this.delimiter = Pair.create(delimit, CharMatcher.is(delimit));
    }
    
    public Pair<Character, CharMatcher> delimiter() {
        return delimiter;
    }
    
    @Override
    public TokenIterator apply(CharSequence input) {
        return new TokenIterator(input);
    }
    
    protected static enum State {
        START,
        TOKEN,
        QUOTED,
        ESCAPED;
    }

    public class TokenIterator extends AbstractIterator<Pair<Integer, String>> {
        
        protected final CharSequence input;
        protected int index;
        
        public TokenIterator(CharSequence input) {
            this.input = checkNotNull(input);
            this.index = 0;
        }
        
        public CharSequence getInput() {
            return input;
        }
        
        public int getIndex() {
            return index;
        }

        @Override
        protected Pair<Integer, String> computeNext() {
            logger.entry(input, index);
            Pair<Integer, String> token = null;
            StringBuilder tokenBuilder = new StringBuilder();
            Integer tokenStart = null;
            LinkedList<State> states = Lists.newLinkedList();
            states.push(State.START);
            while ((token == null) && (index <= input.length())) {
                if (index < input.length()) {
                    char c = input.charAt(index);
                    if (delimiter.second().matches(c)) {
                        switch (states.peek()) {
                        case START:
                            if (tokenBuilder.length() > 0) {
                                token = Pair.create(tokenStart, tokenBuilder.toString());
                            }
                            break;
                        case TOKEN:
                            token = Pair.create(tokenStart, tokenBuilder.toString());
                            states.pop();
                            break;
                        case QUOTED:
                            tokenBuilder.append(c);
                            break;
                        case ESCAPED:
                            tokenBuilder.append(c);
                            states.pop(); 
                            break;
                        }
                    } else if (QUOTE_CHAR == c) {
                        switch (states.peek()) {
                        case START:
                            states.push(State.QUOTED);
                            tokenStart = index;
                            break;
                        case TOKEN:
                            throw new IllegalArgumentException("Embedded quote must be escaped");
                        case QUOTED:
                            token = Pair.create(tokenStart, tokenBuilder.toString());
                            states.pop(); 
                            break;
                        case ESCAPED:
                            tokenBuilder.append(c);
                            states.pop(); 
                            break;
                        }
                    } else if (ESCAPE_CHAR == c) {
                        switch (states.peek()) {
                        case START:
                        case TOKEN:
                        case QUOTED:
                            states.push(State.ESCAPED);
                            tokenStart = index;
                            break;
                        case ESCAPED:
                            tokenBuilder.append(c);
                            states.pop(); 
                            break;
                        }
                    } else {
                        switch (states.peek()) {
                        case START:
                            tokenBuilder.append(c);
                            states.push(State.TOKEN);
                            tokenStart = index;
                            break;
                        case TOKEN:
                        case QUOTED:
                            tokenBuilder.append(c);
                            break;
                        case ESCAPED:
                            tokenBuilder.append(c);
                            states.pop(); 
                            break;
                        }
                    }
                } else {
                    switch (states.peek()) {
                    case QUOTED:
                        throw new IllegalArgumentException("Incomplete quote");
                    case ESCAPED:
                        throw new IllegalArgumentException("Incomplete escape");
                    default:
                        // complete the last token
                        if (tokenBuilder.length() > 0) {
                            token = Pair.create(tokenStart, tokenBuilder.toString());
                        }
                        break;
                    }
                }
                
                index++;
            }
            
            if (token == null) {
                return logger.exit(endOfData());
            } else {
                return logger.exit(token);
            }
        }
    }
}

@@ -626,36 +626,84 @@
 r(SPACE);%0A    %7D%0A
+%0A
     
+protected static char SINGLE_QUOTE_CHAR = '%5C'';
 %0A    protected s
@@ -713,16 +713,23 @@
 ic char 
+DOUBLE_
 QUOTE_CH
@@ -1394,16 +1394,46 @@
         
+SINGLE_QUOTED,%0A        DOUBLE_
 QUOTED,%0A
@@ -3031,32 +3031,1037 @@
            case 
+SINGLE_QUOTED:%0A                        case DOUBLE_QUOTED:%0A                            tokenBuilder.append(c);%0A                            break;%0A                        case ESCAPED:%0A                            tokenBuilder.append(c);%0A                            states.pop(); %0A                            break;%0A                        %7D%0A                    %7D else if (SINGLE_QUOTE_CHAR == c) %7B%0A                        switch (states.peek()) %7B%0A                        case START:%0A                            states.push(State.SINGLE_QUOTED);%0A                            tokenStart = index;%0A                            break;%0A                        case TOKEN:%0A                            throw new IllegalArgumentException(%22Embedded quote must be escaped%22);%0A                        case SINGLE_QUOTED:%0A                            token = Pair.create(tokenStart, tokenBuilder.toString());%0A                            states.pop(); %0A                            break;%0A                        case DOUBLE_
 QUOTED:%0A        
@@ -4364,16 +4364,23 @@
 lse if (
+DOUBLE_
 QUOTE_CH
@@ -4521,16 +4521,23 @@
 h(State.
+DOUBLE_
 QUOTED);
@@ -4775,32 +4775,170 @@
            case 
+SINGLE_QUOTED:%0A                            tokenBuilder.append(c);%0A                            break;%0A                        case DOUBLE_
 QUOTED:%0A        
@@ -5480,32 +5480,83 @@
            case 
+DOUBLE_QUOTED:%0A                        case SINGLE_
 QUOTED:%0A        
@@ -6240,32 +6240,83 @@
            case 
+DOUBLE_QUOTED:%0A                        case SINGLE_
 QUOTED:%0A        
@@ -6672,32 +6672,32 @@
 tates.peek()) %7B%0A
-
                 
@@ -6705,16 +6705,63 @@
    case 
+DOUBLE_QUOTED:%0A                    case SINGLE_
 QUOTED:%0A

allow single quotes