/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2013, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.core.pattern.parser;

import java.util.ArrayList;
import java.util.List;

import ch.qos.logback.core.pattern.util.AsIsEscapeUtil;
import ch.qos.logback.core.pattern.util.IEscapeUtil;

import static ch.qos.logback.core.CoreConstants.CURLY_RIGHT;


import static ch.qos.logback.core.CoreConstants.ESCAPE_CHAR;
import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
import static ch.qos.logback.core.CoreConstants.SINGLE_QUOTE_CHAR;
import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;


import ch.qos.logback.core.pattern.parser.TokenStream.TokenizerState;
import ch.qos.logback.core.spi.ScanException;

public class OptionTokenizer {

  private final static int EXPECTING_STATE = 0;
  private final static int RAW_COLLECTING_STATE = 1;
  private final static int QUOTED_COLLECTING_STATE = 2;


  final IEscapeUtil escapeUtil;
  final TokenStream tokenStream;
  final String pattern;
  final int patternLength;

  char quoteChar;
  int state = EXPECTING_STATE;

  OptionTokenizer(TokenStream tokenStream) {
    this(tokenStream, new AsIsEscapeUtil());
  }

  OptionTokenizer(TokenStream tokenStream, IEscapeUtil escapeUtil) {
    this.tokenStream = tokenStream;
    this.pattern = tokenStream.pattern;
    this.patternLength = tokenStream.patternLength;
    this.escapeUtil = escapeUtil;
  }

  void tokenize(char firstChar, List<Token> tokenList) throws ScanException {
    StringBuffer buf = new StringBuffer();
    List<String> optionList = new ArrayList<String>();
    char c = firstChar;

    while (tokenStream.pointer < patternLength) {
      switch (state) {
        case EXPECTING_STATE:
          switch (c) {
            case ' ':
            case '\t':
            case '\r':
            case '\n':
            case COMMA_CHAR:
              break;
            case SINGLE_QUOTE_CHAR:
            case DOUBLE_QUOTE_CHAR:
              state = QUOTED_COLLECTING_STATE;
              quoteChar = c;
              break;
            case CURLY_RIGHT:
              emitOptionToken(tokenList, optionList);
              return;
            default:
              buf.append(c);
              state = RAW_COLLECTING_STATE;
          }
          break;
        case RAW_COLLECTING_STATE:
          switch (c) {
            case COMMA_CHAR:
              optionList.add(buf.toString().trim());
              buf.setLength(0);
              state = EXPECTING_STATE;
              break;
            case CURLY_RIGHT:
              optionList.add(buf.toString().trim());
              emitOptionToken(tokenList, optionList);
              return;
            default:
              buf.append(c);
          }
          break;
        case QUOTED_COLLECTING_STATE:
          if (c == quoteChar) {
            optionList.add(buf.toString());
            buf.setLength(0);
            state = EXPECTING_STATE;
          } else if (c == ESCAPE_CHAR) {
            escape(String.valueOf(quoteChar), buf);
          } else {
            buf.append(c);
          }

          break;
      }

      c = pattern.charAt(tokenStream.pointer);
      tokenStream.pointer++;
    }


    // EOS
    if (c == CURLY_RIGHT) {
      if(state == EXPECTING_STATE) {
        emitOptionToken(tokenList, optionList);
      } else if(state == RAW_COLLECTING_STATE){
        optionList.add(buf.toString().trim());
        emitOptionToken(tokenList, optionList);
      } else {
        throw new ScanException("Unexpected end of pattern string in OptionTokenizer");
      }
    } else {
      throw new ScanException("Unexpected end of pattern string in OptionTokenizer");
    }
  }

  void emitOptionToken( List<Token> tokenList, List<String> optionList) {
     tokenList.add(new Token(Token.OPTION, optionList));
     tokenStream.state = TokenizerState.LITERAL_STATE;
  }

  void escape(String escapeChars, StringBuffer buf) {
    if ((tokenStream.pointer < patternLength)) {
      char next = pattern.charAt(tokenStream.pointer++);
      escapeUtil.escape(escapeChars, buf, next, tokenStream.pointer);
    }
  }
}
