package sharin.csv.parser;

class BasicCsvAutomaton {

    private enum State {
        INITIAL, NEW_RECORD, NEW_RECORD_CR, SEPARATED, SEPARATED_CR, VALUE, VALUE_CR, QUOTED, ESCAPED, ESCAPED_CR, FINAL
    };

    private final CsvHandler handler;

    private final char separator;

    private State state;

    public BasicCsvAutomaton(CsvHandler handler, char separator) {
        this.handler = handler;
        this.separator = separator;
        state = State.INITIAL;
    }

    public void put(int ch) {

        switch (state) {

        case INITIAL:
            putOnInitial(ch);
            break;

        case NEW_RECORD:
            putOnNewRecord(ch);
            break;

        case NEW_RECORD_CR:
            putOnNewRecordCR(ch);
            break;

        case VALUE:
            putOnValue(ch);
            break;

        case VALUE_CR:
            putOnValueCR(ch);
            break;

        case SEPARATED:
            putOnSeparated(ch);
            break;

        case SEPARATED_CR:
            putOnSeparatedCR(ch);
            break;

        case QUOTED:
            putOnQuoted(ch);
            break;

        case ESCAPED:
            putOnEscaped(ch);
            break;

        case ESCAPED_CR:
            putOnEscapedCR(ch);
            break;

        default:
            /* do nothing */
            break;
        }
    }

    private void putOnInitial(int ch) {
        handler.startDocument();
        putOnNewRecord(ch);
    }

    private void putOnNewRecord(int ch) {

        if (ch == separator) {
            handler.startRecord();
            handler.startValue();
            handler.endValue();
            state = State.SEPARATED;

        } else if (ch == '"') {
            handler.startRecord();
            handler.startValue();
            state = State.QUOTED;

        } else if (ch == '\r') {
            handler.startRecord();
            handler.endRecord();
            state = State.NEW_RECORD_CR;

        } else if (ch == '\n') {
            handler.startRecord();
            handler.endRecord();
            state = State.NEW_RECORD;

        } else if (ch == -1) {
            handler.endDocument();
            state = State.FINAL;

        } else {
            handler.startRecord();
            handler.startValue();
            handler.character((char) ch);
            state = State.VALUE;
        }
    }

    private void putOnNewRecordCR(int ch) {

        if (ch == '\n') {
            state = State.NEW_RECORD;

        } else {
            putOnNewRecord(ch);
        }
    }

    private void putOnValue(int ch) {

        if (ch == separator) {
            handler.endValue();
            state = State.SEPARATED;

        } else if (ch == '"') {
            handler.character((char) ch);
            state = State.VALUE;

        } else if (ch == '\r') {
            handler.endValue();
            handler.endRecord();
            state = State.VALUE_CR;

        } else if (ch == '\n') {
            handler.endValue();
            handler.endRecord();
            state = State.NEW_RECORD;

        } else if (ch == -1) {
            handler.endValue();
            handler.endRecord();
            handler.endDocument();
            state = State.FINAL;

        } else {
            handler.character((char) ch);
            state = State.VALUE;
        }
    }

    private void putOnValueCR(int ch) {

        if (ch == '\n') {
            state = State.NEW_RECORD;

        } else {
            putOnNewRecord(ch);
        }
    }

    private void putOnSeparated(int ch) {

        if (ch == separator) {
            handler.startValue();
            handler.endValue();
            state = State.SEPARATED;

        } else if (ch == '"') {
            handler.startValue();
            state = State.QUOTED;

        } else if (ch == '\r') {
            handler.startValue();
            handler.endValue();
            handler.endRecord();
            state = State.SEPARATED_CR;

        } else if (ch == '\n') {
            handler.startValue();
            handler.endValue();
            handler.endRecord();
            state = State.NEW_RECORD;

        } else if (ch == -1) {
            handler.startValue();
            handler.endValue();
            handler.endRecord();
            handler.endDocument();
            state = State.FINAL;

        } else {
            handler.startValue();
            handler.character((char) ch);
            state = State.VALUE;
        }
    }

    private void putOnSeparatedCR(int ch) {

        if (ch == '\n') {
            state = State.NEW_RECORD;

        } else {
            putOnNewRecord(ch);
        }
    }

    private void putOnQuoted(int ch) {

        if (ch == separator) {
            handler.character((char) ch);
            state = State.QUOTED;

        } else if (ch == '"') {
            state = State.ESCAPED;

        } else if (ch == '\r') {
            handler.character((char) ch);
            state = State.QUOTED;

        } else if (ch == '\n') {
            handler.character((char) ch);
            state = State.QUOTED;

        } else if (ch == -1) {
            handler.endValue();
            handler.endRecord();
            handler.endDocument();
            state = State.FINAL;

        } else {
            handler.character((char) ch);
            state = State.QUOTED;
        }
    }

    private void putOnEscaped(int ch) {

        if (ch == separator) {
            handler.endValue();
            state = State.SEPARATED;

        } else if (ch == '"') {
            handler.character((char) ch);
            state = State.QUOTED;

        } else if (ch == '\r') {
            handler.endValue();
            handler.endRecord();
            state = State.ESCAPED_CR;

        } else if (ch == '\n') {
            handler.endValue();
            handler.endRecord();
            state = State.NEW_RECORD;

        } else if (ch == -1) {
            handler.endValue();
            handler.endRecord();
            handler.endDocument();
            state = State.FINAL;

        } else {
            handler.character((char) ch);
            state = State.VALUE;
        }
    }

    private void putOnEscapedCR(int ch) {

        if (ch == '\n') {
            state = State.NEW_RECORD;

        } else {
            putOnNewRecord(ch);
        }
    }
}
