package cparser;

import java.util.*;

import cparser.Tokenizer.Token;
import cparser.Log;
import cparser.AST.VariableDeclaration;

public class Parser {
  private Tokenizer.TokenSet tokenSet;
  private Log log;
  private Tokenizer.Token[] tokens;

  private List<Object> statements = new ArrayList<>();
  private List<VariableDeclaration> variableDeclarations = new ArrayList<>();
  private List<AST.Span> commentStack = new ArrayList<>();
  private List<AST.Error> errors = new ArrayList<>();

  public Parser(Tokenizer.TokenSet tokenSet, Log log) {
    this.tokenSet = tokenSet;
    this.statements = new ArrayList<>();
    this.tokens = tokenSet.getTokens();
  }

  void log(String msg) {
    if (log != null) {
      log.log(msg);
    }
  }

  int index = 0;

  public List<VariableDeclaration> getVariableDeclarations() {
    return variableDeclarations;
  }

  private static class Result<T> {
    public final T value;
    public final AST.Span span;

    public Result(T value, AST.Span span) {
      this.value = value;
      this.span = span;
    }

    public Result() {
      this.value = null;
      this.span = new AST.Span();
    }

    public boolean isValid() {
      return span.isValid();
    }

    public static <T> Result<T> none() {
      return new Result<T>();
    }

    public Result<Object> toGeneric() {
      return new Result<Object>(value, span);
    }
  }

  boolean applyResult(Result<?> result) {
    if (result.span.isValid()) {
      index = result.span.endOffset + 1;

      if (result.value instanceof VariableDeclaration) {
        variableDeclarations.add((VariableDeclaration) result.value);
      }

      return true;
    }
    return false;
  }

  private void addError(AST.Span span, String message) {
    errors.add(new AST.Error(span, message));
  }

  private void addError(Tokenizer.Token token, String message) {
    addError(token.getSpan(), message);
  }

  public void parse() {
    for (index = 0; index < tokens.length;) {
      Tokenizer.Token token = tokens[index];
      if (token.type == Tokenizer.TokenType.BLOCK_COMMENT || token.type == Tokenizer.TokenType.COMMENT) {
        commentStack.add(token.getSpan());
        index++;
        continue;
      } else if (token.type == Tokenizer.TokenType.HASH) {
        Result<AST.PreprocessorExpression> result = parsePreprocessorExpression();
        if (result.span.isValid()) {
          index = result.span.endOffset + 1;
        } else {
          addError(token, "Invalid preprocessor expression");
          index++;
        }
      } else {
        Result<?> stmt = parseStmt();
        if (applyResult(stmt))
          continue;
        addError(token, "Invalid statement");
        index++;
      }
    }
  }

  private Result<VariableDeclaration> parseVarDecl() {
    int startIndex = index;
    StringBuilder type = new StringBuilder();
    String identifier = null;
    boolean isPointer = false;
    boolean isReference = false;
    boolean isArray = false;
    boolean isFunction = false;
    int arraySize = -1;

    boolean haveTypeIdentifier = false;
    boolean haveIdentifier = false;

    int parenDepth = 0;
    int idxStart = 0;
    int idxDepth = 0;

    // Parse type
    while (index < tokens.length) {
      Token token = tokens[index];
      if (token.type == Tokenizer.TokenType.IDENTIFIER || token.type == Tokenizer.TokenType.OTHER) {
        // Already have a type identifier, so this is most likely a variable name
        if (!haveTypeIdentifier) {
          type.append(tokenSet.getTextNoNewlines(token));
          haveTypeIdentifier = true;
        } else if (!haveIdentifier) {
          identifier = tokenSet.getTextNoNewlines(token);
          haveIdentifier = true;
          if (parenDepth == 0)
            break;
        } else {
          // Unknown
          addError(token, "Unknown token after identifier");
        }
        index++;
      } else if (token.type == Tokenizer.TokenType.STAR) {
        isPointer = true;
        type.append("*");
        index++;
      } else if (token.type == Tokenizer.TokenType.AMPERSAND) {
        isReference = true;
        type.append("&");
        index++;
      } else if (token.type == Tokenizer.TokenType.L_PAREN) {
        parenDepth++;
        index++;
      } else if (token.type == Tokenizer.TokenType.R_PAREN) {
        parenDepth--;
        index++;
      } else if (token.type == Tokenizer.TokenType.L_IDX) {
        idxDepth++;
        idxStart = token.ofs + 1;
        if (haveIdentifier) {
          // Parse function parameters?
        }
        break;
      } else if (token.type == Tokenizer.TokenType.R_IDX) {
        idxDepth--;
        if (idxDepth == 0) {
          String idxVal = tokenSet.getTextNoNewlines(idxStart, (token.ofs - 1) - idxStart);
          type.append("[" + idxVal + "]");
        }
        break;
      } else {
        break;
      }
    }

    AST.Span span = new AST.Span(tokens[startIndex].ofs, tokens[index].getEnd());
    VariableDeclaration varDecl = new VariableDeclaration(
        type.toString().trim(),
        identifier,
        isPointer,
        isReference,
        isArray,
        arraySize,
        isFunction,
        span);

    return new Result<>(varDecl, span);
  }

  private Result<?> parseStmt() {
    int startIndex = index;
    for (int i = startIndex; i < tokens.length; i++) {
      Tokenizer.Token token = tokens[i];

      Result<VariableDeclaration> varDeclResult = parseVarDecl();
      if (varDeclResult.isValid())
        return varDeclResult;

      // if (token.type == Tokenizer.TokenType.L_PAREN && idStack.size() > 0) {
      // // Function call?
      // } else if (token.type == Tokenizer.TokenType.SEMICOLON) {
      // boolean isVarAssign = false;
      // for (int j = startIndex; j < i; j++) {
      // if (tokens[j].type == Tokenizer.TokenType.EQUALS) {
      // isVarAssign = true;
      // }
      // }
      // } else if (token.type == Tokenizer.TokenType.L_BRACE) {
      // boolean isVarAssign = false;
      // for (int j = startIndex; j < i; j++) {
      // if (tokens[j].type == Tokenizer.TokenType.L_PAREN) {
      // int endIndex = findClosingParenthesis(j);
      // if (endIndex != -1) {
      // index = endIndex;
      // }
      // }
      // }
      // }
    }

    return Result.none();
    // if (index + 1 < tokens.length && tokens[index + 1].type ==
    // Tokenizer.TokenType.L_PAREN) {
    // // Function call or declaration/definition
    // if (index > 0 && (tokens[index - 1].type == Tokenizer.TokenType.IDENTIFIER ||
    // tokens[index - 1].type == Tokenizer.TokenType.OTHER)) {
    // // Function declaration or definition
    // index = parseFunctionDeclaration();
    // } else {
    // // Function call
    // index = parseFunctionCall();
    // }
    // } else {
    // // Variable reference
    // index = parseVariableReference();
    // }
  }

  private int parseVarDecl(int startIndex, int endIndex) {
    if (tokens[startIndex].type == Tokenizer.TokenType.R_PAREN) {
      return startIndex;
    }
    return startIndex + 1;
  }

  // Try to parse prep expression
  private Result<AST.PreprocessorExpression> parsePreprocessorExpression() {
    int startIndex = index;
    int index = startIndex;
    if (tokenSet.tokens[index].type == Tokenizer.TokenType.HASH) {
      int startLine = tokenSet.getLine(index);
      while (index < tokenSet.tokens.length) {
        if (tokenSet.getLine(index) > startLine) {
          break;
        }
        index++;
      }
      // Find first next line token
      index--;
    }

    AST.Span span = new AST.Span(startIndex, index);
    return new Result(new AST.PreprocessorExpression(span), span);
  }

  // private ArgumentList parseArgumentList(int startIndex, int endIndex) {
  // List<Object> arguments = new ArrayList<>();
  // for (int i = startIndex; i < endIndex; i++) {
  // if (tokens[i].type == Tokenizer.TokenType.COMMA) {

  // }
  // }
  // return new ArgumentList(arguments);
  // }

  // // Try to parse function declaration and return the ending token index
  // private int parseFunctionDeclaration() {
  // Tokenizer.Token[] tokens = tokenSet.getTokens();
  // String name = tokenSet.getTextNoNewlines(tokens[index]);
  // int endIndex = findClosingParenthesis(index + 1);

  // if (endIndex == -1)
  // return index;

  // boolean isDefinition = false;
  // if (endIndex + 1 < tokens.length && tokens[endIndex + 1].type ==
  // Tokenizer.TokenType.L_BRACE) {
  // isDefinition = true;
  // endIndex = findClosingBrace(endIndex + 1);
  // }

  // if (endIndex == -1)
  // return index;

  // Function function = new Function(name, tokens[index].ofs,
  // tokens[endIndex].ofs + tokens[endIndex].len,
  // isDefinition);
  // functions.add(function);
  // return endIndex - 1;
  // }

  // // Try to parse function call and return the ending token index
  // private int parseFunctionCall() {
  // Tokenizer.Token[] tokens = tokenSet.getTokens();
  // String name = tokenSet.getTextNoNewlines(tokens[index]);
  // int endIndex = findClosingParenthesis(index + 1);
  // if (endIndex == -1)
  // return index;

  // FunctionCall functionCall = new FunctionCall(name, tokens[index].ofs,
  // tokens[endIndex].ofs + tokens[endIndex].len);
  // functionCalls.add(functionCall);
  // return endIndex - 1;
  // }

  // // Try to parse variable reference and add it to the list
  // private int parseVariableReference() {
  // Tokenizer.Token token = tokenSet.getTokens()[index];
  // String name = tokenSet.getTextNoNewlines(token);
  // Variable variable = new Variable(name, token.ofs, token.ofs + token.len);
  // variables.add(variable);
  // return index + 1;
  // }

  private int findClosingParenthesis(int startIndex) {
    Tokenizer.Token[] tokens = tokenSet.getTokens();
    int parenCount = 1;
    for (int i = startIndex + 1; i < tokens.length; i++) {
      if (tokens[i].type == Tokenizer.TokenType.L_PAREN) {
        parenCount++;
      } else if (tokens[i].type == Tokenizer.TokenType.R_PAREN) {
        parenCount--;
        if (parenCount == 0) {
          return i;
        }
      }
    }
    return -1;
  }

  private int findClosingBrace(int startIndex) {
    Tokenizer.Token[] tokens = tokenSet.getTokens();
    int braceCount = 1;
    for (int i = startIndex + 1; i < tokens.length; i++) {
      if (tokens[i].type == Tokenizer.TokenType.L_BRACE) {
        braceCount++;
      } else if (tokens[i].type == Tokenizer.TokenType.R_BRACE) {
        braceCount--;
        if (braceCount == 0) {
          return i;
        }
      }
    }
    return -1;
  }
}
