package datalog_ra.parser;

import java.io.*;

public class Lexer {
    private InputStream input;
    private Token currToken;
    private String identifierStr;
    private int currChar;
    private int line, column, currLine, currColumn;

    public Lexer(InputStream input) {
        this.input = input;
        currToken = null;
        currChar = ' ';
        identifierStr = null;
        line = 1;
        column = 0;
    }

    /**
     * Reads next token from the input stream. If constant or variable is read,
     * assigns its value to identifierStr.
     */
    public void next() {
        identifierStr = null;
        try {
            while (currChar == ' ' || currChar == '\t' || currChar == '\n' || currChar == '%') {
                if (currChar == '%') {
                    // skip comments
                    while (currChar != '\n' && currChar != -1) {
                        nextChar();
                    }
                } else {
                    nextChar();
                }
            }

            currLine = line;
            currColumn = column;
            if (currChar == '\'') {
                identifierStr = "";
                nextChar(); // skip opening '
                while (currChar != '\'') {
                    identifierStr += (char)currChar;
                    nextChar();
                }
                nextChar(); // skip closing '
                currToken = Token.constant;
            } else if (isAlphNum(currChar)) {
                identifierStr = "";
                while (isAlphNum(currChar)) {
                    identifierStr += (char)currChar;
                    nextChar();
                }
                if (identifierStr.charAt(0) >= 'A' && identifierStr.charAt(0) <= 'Z' || identifierStr.equals("_")) {
                    currToken = Token.variable;
                } else {
                    currToken = Token.constant;
                }
                if (identifierStr.equals("not")) {
                    currToken = Token.not;
                    identifierStr = null;
                }
            } else {
                switch (currChar) {
                    case '(':
                        currToken = Token.lparen;
                        break;
                    case ')':
                        currToken = Token.rparen;
                        break;
                    case ',':
                        currToken = Token.comma;
                        break;
                    case '.':
                        currToken = Token.dot;
                        break;
                    case ':':
                        currToken = Token.colon_dash;
                        break;
                    case '\\':
                        currToken = Token.not;
                        break;
                    case '=':
                        currToken = Token.equal;
                        break;
                    case -1:
                        currToken = Token.eof;
                        break;
                    default:
                        currToken = Token.unknown;
                        break;
                }
                nextChar();
                if (currToken == Token.colon_dash) {
                    if (currChar == '-') {
                        nextChar();
                    } else {
                        currToken = Token.unknown;
                    }
                }
                if (currToken == Token.not) {
                    if (currChar == '+') {
                        nextChar();
                    } else if (currChar == '=') {
                        currToken = Token.not_equal;
                        nextChar();
                    } else {
                        currToken = Token.unknown;
                    }
                }
            }
        } catch (IOException e) {
            currToken = Token.eof;
        }
        //System.out.println("next token: " + currToken.toString() + ", " + (identifierStr != null ? identifierStr : ""));
    }

    public Token getToken() {
        return currToken;
    }

    public String getIdentifierStr() {
        return identifierStr;
    }

    public int getLine() {
        return currLine;
    }

    public int getColumn() {
        return currColumn;
    }

    /**
     * Reads next byte from the input stream. Updates line number and column number.
     * @throws IOException
     */
    private void nextChar() throws IOException {
        if (currChar == '\n') {
            line++;
            column = 1;
        } else {
            column++;
        }
        int c = currChar;
        currChar = input.read();
    }

    private boolean isAlphNum(int c) {
        return ((currChar >= 'A' && currChar <= 'Z') ||
                (currChar >= 'a' && currChar <= 'z') ||
                (currChar >= '0' && currChar <= '9') ||
                (currChar == '_' || currChar == '-'));
    }
}
