package seedu.todo.controllers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import seedu.todo.commons.exceptions.UnmatchedQuotesException;

public class Tokenizer {

    private final static String QUOTE = "\"";

    private static class TokenizedString {
        public String string;
        public boolean isToken;
        public boolean isQuote;

        TokenizedString(String string, boolean isToken, boolean isQuote) {
            this.string = string;
            this.isToken = isToken;
            this.isQuote = isQuote;
        }

        @Override
        public String toString() {
            return String.format("TokenizedString(%s, %s, %s)", this.string, isToken, isQuote);
        }
    }

    public static Map<String, String[]> tokenize(Map<String, String[]> tokenDefinitions, String inputCommand)
            throws UnmatchedQuotesException {
        
        // Generate token -> tokenType mapping and list of tokens
        List<String> tokens = new ArrayList<String>();
        HashMap<String, String> getTokenType = new HashMap<String, String>();
        for (Map.Entry<String, String[]> tokenDefinition : tokenDefinitions.entrySet()) {
            String tokenType = tokenDefinition.getKey();
            for (String token : tokenDefinition.getValue()) {
                tokens.add(token);
                getTokenType.put(token, tokenType);
            }
        }

        if (inputCommand.length() == 0)
            return null;

        // Split inputCommand into arraylist of chunks

        if (StringUtils.countMatches(inputCommand, QUOTE) % 2 == 1)
            throw new UnmatchedQuotesException("Unmatched double-quotes detected.");

        // --- Split by quotes
        String[] splitString = inputCommand.split(QUOTE);

        // If first char is QUOTE, then first element is a quoted string.
        List<TokenizedString> tokenizedSplitString = new ArrayList<TokenizedString>();
        for (int i = 0; i < splitString.length; i++) {
            tokenizedSplitString.add(new TokenizedString(splitString[i].trim(), false, (i % 2 == 1)));
        }
        
        // --- Split by tokens
        Map<String, Integer> tokenIndices = new HashMap<String, Integer>();
        for (int i = 0; i < tokenizedSplitString.size(); i++) { // Java doesn't eager-evaluate the terminating condition
            TokenizedString currString = tokenizedSplitString.get(i);
            if (currString.isQuote)
                continue;
            
            // Record token.
            if (currString.isToken) {
                tokenIndices.put(getTokenType.get(currString.string), i);
                tokens.remove(currString.string);
                continue;
            }
            
            // Try to match all the tokens
            for (String token : tokens) {
                Matcher m = Pattern.compile(String.format("\\b%s\\b", token)).matcher(currString.string);
                if (!m.find())
                    continue;
                
                // Found. Replace current element with split elements.
                String preString = currString.string.substring(0, m.start()).trim();
                String postString = currString.string.substring(m.end(), currString.string.length()).trim();
                
                tokenizedSplitString.remove(i);
                List<TokenizedString> replacedSplitStrings = new ArrayList<TokenizedString>();
                if (!preString.isEmpty())
                    replacedSplitStrings.add(new TokenizedString(preString, false, false));
                replacedSplitStrings.add(new TokenizedString(token, true, false));
                if (!postString.isEmpty())
                    replacedSplitStrings.add(new TokenizedString(postString, false, false));
                tokenizedSplitString.addAll(i, replacedSplitStrings);
                
                // Restart outer loop at current index.
                i--;
                break;
            }   
        }
        
        // Get arraylist of indices
        // Get dictionary of tokenType -> index
        
        Map<String, String[]> parsedResult = new HashMap<String, String[]>();
        for (Map.Entry<String, Integer> tokenIndex : tokenIndices.entrySet()) {
            String tokenType = tokenIndex.getKey();
            String token = tokenizedSplitString.get(tokenIndex.getValue()).string;
            String tokenField = null;
            // Should just EAFP instead of LBYL, but oh well.
            if (tokenIndex.getValue() + 1 < tokenizedSplitString.size() && !tokenizedSplitString.get(tokenIndex.getValue() + 1).isToken)
                tokenField = tokenizedSplitString.get(tokenIndex.getValue() + 1).string;
            parsedResult.put(tokenType, new String[] { token, tokenField });
        }
        
        // Return dictionary of tokenType -> {token, tokenField}
        return parsedResult;
    }

}


@@ -2280,32 +2280,1224 @@
 Split by tokens%0A
+        Map%3CString, Integer%3E tokenIndices = splitByTokens(tokens, getTokenType, tokenizedSplitString);%0A        %0A        // Get arraylist of indices%0A        // Get dictionary of tokenType -%3E index%0A        %0A        Map%3CString, String%5B%5D%3E parsedResult = new HashMap%3CString, String%5B%5D%3E();%0A        for (Map.Entry%3CString, Integer%3E tokenIndex : tokenIndices.entrySet()) %7B%0A            String tokenType = tokenIndex.getKey();%0A            String token = tokenizedSplitString.get(tokenIndex.getValue()).string;%0A            String tokenField = null;%0A            // Should just EAFP instead of LBYL, but oh well.%0A            if (tokenIndex.getValue() + 1 %3C tokenizedSplitString.size() && !tokenizedSplitString.get(tokenIndex.getValue() + 1).isToken)%0A                tokenField = tokenizedSplitString.get(tokenIndex.getValue() + 1).string;%0A            parsedResult.put(tokenType, new String%5B%5D %7B token, tokenField %7D);%0A        %7D%0A        %0A        // Return dictionary of tokenType -%3E %7Btoken, tokenField%7D%0A        return parsedResult;%0A    %7D%0A%0A    private static Map%3CString, Integer%3E splitByTokens(List%3CString%3E tokens, HashMap%3CString, String%3E getTokenType,%0A            List%3CTokenizedString%3E tokenizedSplitString) %7B%0A
         Map%3CStri
@@ -5339,909 +5339,27 @@
     
-%0A        // Get arraylist of indices%0A        // Get dictionary of tokenType -%3E index%0A        %0A        Map%3CString, String%5B%5D%3E parsedResult = new HashMap%3CString, String%5B%5D%3E();%0A        for (Map.Entry%3CString, Integer%3E tokenIndex : tokenIndices.entrySet()) %7B%0A            String tokenType = tokenIndex.getKey();%0A            String token = tokenizedSplitString.get(tokenIndex.getValue()).string;%0A            String tokenField = null;%0A            // Should just EAFP instead of LBYL, but oh well.%0A            if (tokenIndex.getValue() + 1 %3C tokenizedSplitString.size() && !tokenizedSplitString.get(tokenIndex.getValue() + 1).isToken)%0A                tokenField = tokenizedSplitString.get(tokenIndex.getValue() + 1).string;%0A            parsedResult.put(tokenType, new String%5B%5D %7B token, tokenField %7D);%0A        %7D%0A        %0A        // Return dictionary of tokenType -%3E %7Btoken, tokenField%7D%0A        return parsedResult
+return tokenIndices
 ;%0A  

Refactor splitByToken.