package com.igormaznitsa.ideamindmap.lang;

import com.igormaznitsa.ideamindmap.lang.tokens.MMTokens;
import com.igormaznitsa.meta.common.utils.Assertions;
import com.igormaznitsa.mindmap.model.ModelUtils;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LightPsiParser;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiParser;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.psi.tree.IElementType;
import javax.annotation.Nonnull;

public class MMPsiParser implements PsiParser, LightPsiParser {
  @Nonnull
  @Override
  public ASTNode parse(@Nonnull final IElementType root, @Nonnull final PsiBuilder builder) {
    parseLight(root, builder);
    final ASTNode result = builder.getTreeBuilt();
    return result;
  }

  @Override
  public void parseLight(final IElementType root, final PsiBuilder builder) {
    builder.setDebugMode(ApplicationManager.getApplication().isUnitTestMode());
    final PsiBuilder.Marker marker = builder.mark();

    parseHeader(builder);
    parseTopics(builder);

    marker.done(root);
  }

  private void parseHeader(@Nonnull final PsiBuilder builder) {
    boolean doLoop = true;
    while (doLoop && !builder.eof()) {
      final PsiBuilder.Marker marker = builder.mark();

      if (builder.getTokenType() == null) {
        marker.drop();
      } else {
        final IElementType token = builder.getTokenType();

        if (token == MMTokens.HEADER_DELIMITER) {
          marker.done(token);
          doLoop = false;
        } else if (token == MMTokens.HEADER_LINE
            || token == MMTokens.UNKNOWN
            || token == MMTokens.WHITE_SPACE
            || token == MMTokens.ATTRIBUTES) {
          marker.done(token);
        } else {
          throw Assertions.fail("Unexpected header token : " + token);
        }
      }
      builder.advanceLexer();
    }
  }

  private void parseTopics(@Nonnull final PsiBuilder builder) {
    while (!builder.eof()) {
      final PsiBuilder.Marker marker = builder.mark();
      final IElementType token = builder.getTokenType();

      if (token == null) {
        marker.drop();
      } else {
        if (token == MMTokens.TOPIC_LEVEL) {
          final PsiBuilder.Marker levelMarker = builder.mark();
          levelMarker.done(token);
          final int topicLevel = ModelUtils.calcCharsOnStart('#', builder.getTokenText());
          if (topicLevel != 1) {
            marker.done(MMTokens.UNKNOWN);
          } else {
            builder.advanceLexer();
            recursiveParseTopic(builder, topicLevel);
            marker.done(MMTokens.TOPIC);
          }
        } else {
          marker.done(MMTokens.UNKNOWN);
        }
      }
      builder.advanceLexer();
    }
  }

  private int recursiveParseTopic(@Nonnull final PsiBuilder builder, final int level) {

    while (!builder.eof()) {
      final PsiBuilder.Marker marker = builder.mark();

      final IElementType token = builder.getTokenType();

      if (token == null) {
        marker.drop();
      } else {
        if (token == MMTokens.TOPIC_LEVEL) {
          final PsiBuilder.Marker levelMarker = builder.mark();
          levelMarker.done(token);

          final int theTopicLevel = ModelUtils.calcCharsOnStart('#', builder.getTokenText());
          if (theTopicLevel <= 1) {
            marker.done(MMTokens.UNKNOWN);
          } else {
            if (theTopicLevel <= level) {
              marker.rollbackTo();
              return theTopicLevel;
            } else {
              builder.advanceLexer();
              final int parsedTopicLevel = recursiveParseTopic(builder, theTopicLevel);
              marker.done(MMTokens.TOPIC);
              if (parsedTopicLevel < theTopicLevel) {
                return parsedTopicLevel;
              }
              if (parsedTopicLevel == theTopicLevel) {
                continue;
              }
            }
          }
        } else if (token == MMTokens.TOPIC_TITLE || token == MMTokens.CODE_SNIPPET_BODY || token == MMTokens.CODE_SNIPPET_END || token == MMTokens.CODE_SNIPPET_START || token == MMTokens.ATTRIBUTES) {
          marker.done(token);
        } else if (token == MMTokens.EXTRA_TYPE) {
          try {
            if (parseExtraBlock(builder)) {
              continue;
            }
          } finally {
            marker.done(MMTokens.EXTRA_DATA);
          }
        } else if (token == MMTokens.WHITE_SPACE) {
          marker.done(token);
        } else {
          marker.done(MMTokens.UNKNOWN);
        }
      }
      builder.advanceLexer();
    }


    return level;
  }

  private boolean parseExtraBlock(@Nonnull final PsiBuilder builder) {
    // read type
    final PsiBuilder.Marker type = builder.mark();
    if (builder.getTokenType() != MMTokens.EXTRA_TYPE) {
      throw Assertions.fail("Unexpected token " + builder.getTokenType());
    }
    builder.advanceLexer();
    type.done(MMTokens.EXTRA_TYPE);

    boolean dataFound = false;

    // read body
    while (!builder.eof()) {
      final PsiBuilder.Marker marker = builder.mark();
      if (builder.eof() || builder.getTokenType() == null) {
        marker.drop();
        break;
      } else {
        final IElementType token = builder.getTokenType();
        if (token == MMTokens.TOPIC_LEVEL || token == MMTokens.EXTRA_TYPE) {
          marker.rollbackTo();
          return true;
        } else if (token == MMTokens.EXTRA_BODY || token == MMTokens.WHITE_SPACE) {
          if (dataFound && token == MMTokens.EXTRA_BODY) {
            builder.advanceLexer();
            marker.done(MMTokens.UNKNOWN);
            break;
          } else {
            builder.advanceLexer();
            marker.done(token);
            dataFound = dataFound || token == MMTokens.EXTRA_BODY;
          }
        } else {
          marker.done(MMTokens.UNKNOWN);
          break;
        }
      }
    }
    return false;
  }

}