package com.github.jknack.antlr4ide.issues;

import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;

import java.util.Arrays;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import com.github.jknack.antlr4ide.lang.Grammar;
import com.github.jknack.antlr4ide.lang.GrammarType;
import com.github.jknack.antlr4ide.lang.LangPackage;
import com.github.jknack.antlr4ide.lang.LexerCommand;
import com.github.jknack.antlr4ide.lang.LexerCommandArg;
import com.github.jknack.antlr4ide.lang.LexerCommandExpr;
import com.github.jknack.antlr4ide.lang.LexerRule;
import com.github.jknack.antlr4ide.lang.Mode;
import com.github.jknack.antlr4ide.lang.Rule;
import com.github.jknack.antlr4ide.lang.V3Token;
import com.github.jknack.antlr4ide.lang.V4Token;
import com.github.jknack.antlr4ide.validation.Antlr4Validator;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Antlr4Validator.class, NodeModelUtils.class })
public class Issue29 {

  @Test
  public void commandWithModeRefOK() throws Exception {
    Grammar grammar = createMock(Grammar.class);
    expect(grammar.getType()).andReturn(GrammarType.DEFAULT);

    Rule rule = createMock(Rule.class);
    expect(rule.eContainer()).andReturn(grammar);

    Mode ref = createMock(Mode.class);
    expect(ref.getId()).andReturn("VAR").times(2);

    LexerCommandExpr args = createMock(LexerCommandExpr.class);
    expect(args.getRef()).andReturn(ref);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getArgs()).andReturn(args);
    expect(command.eContainer()).andReturn(rule);

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "error");

    Object[] mocks = {grammar, rule, command, args, ref, validator };

    replay(mocks);

    validator.commandWithUnrecognizedConstantValue(command);

    verify(mocks);
  }

  @Test
  public void commandWithLexerRuleRefOK() throws Exception {
    Grammar grammar = createMock(Grammar.class);
    expect(grammar.getType()).andReturn(GrammarType.DEFAULT);

    Rule rule = createMock(Rule.class);
    expect(rule.eContainer()).andReturn(grammar);

    LexerRule ref = createMock(LexerRule.class);
    expect(ref.getName()).andReturn("VAR").times(2);

    LexerCommandExpr args = createMock(LexerCommandExpr.class);
    expect(args.getRef()).andReturn(ref);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getArgs()).andReturn(args);
    expect(command.eContainer()).andReturn(rule);

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "error");

    Object[] mocks = {grammar, rule, command, args, ref, validator };

    replay(mocks);

    validator.commandWithUnrecognizedConstantValue(command);

    verify(mocks);
  }

  @Test
  public void warnCommandWithV3Token() throws Exception {
    Grammar grammar = createMock(Grammar.class);
    expect(grammar.getType()).andReturn(GrammarType.DEFAULT);

    Rule rule = createMock(Rule.class);
    expect(rule.eContainer()).andReturn(grammar);
    expect(rule.getName()).andReturn("RULE");

    V3Token ref = createMock(V3Token.class);
    expect(ref.getId()).andReturn("VAR");

    EStructuralFeature feature = createMock(EStructuralFeature.class);

    EClass eClass = createMock(EClass.class);
    expect(eClass.getEStructuralFeature("ref")).andReturn(feature);

    LexerCommandExpr args = createMock(LexerCommandExpr.class);
    expect(args.getRef()).andReturn(ref);
    expect(args.eClass()).andReturn(eClass);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getArgs()).andReturn(args);
    expect(command.eContainer()).andReturn(rule);

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "warning");

    PowerMock.expectPrivate(validator, "warning",
        "rule 'RULE' contains a lexer command with an unrecognized " +
            "constant value; lexer interpreters may produce incorrect output",
        args, feature);

    Object[] mocks = {grammar, rule, command, args, ref, eClass, feature, validator };

    replay(mocks);

    validator.commandWithUnrecognizedConstantValue(command);

    verify(mocks);
  }

  @Test
  public void warnCommandWithV4Token() throws Exception {
    Grammar grammar = createMock(Grammar.class);
    expect(grammar.getType()).andReturn(GrammarType.DEFAULT);

    Rule rule = createMock(Rule.class);
    expect(rule.eContainer()).andReturn(grammar);
    expect(rule.getName()).andReturn("RULE");

    V4Token ref = createMock(V4Token.class);
    expect(ref.getName()).andReturn("VAR");

    EStructuralFeature feature = createMock(EStructuralFeature.class);

    EClass eClass = createMock(EClass.class);
    expect(eClass.getEStructuralFeature("ref")).andReturn(feature);

    LexerCommandExpr args = createMock(LexerCommandExpr.class);
    expect(args.getRef()).andReturn(ref);
    expect(args.eClass()).andReturn(eClass);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getArgs()).andReturn(args);
    expect(command.eContainer()).andReturn(rule);

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "warning");

    PowerMock.expectPrivate(validator, "warning",
        "rule 'RULE' contains a lexer command with an unrecognized " +
            "constant value; lexer interpreters may produce incorrect output",
        args, feature);

    Object[] mocks = {grammar, rule, command, args, ref, eClass, feature, validator };

    replay(mocks);

    validator.commandWithUnrecognizedConstantValue(command);

    verify(mocks);
  }

  @Test
  public void warnCommandWithDefaultChannel() throws Exception {
    Grammar grammar = createMock(Grammar.class);
    expect(grammar.getType()).andReturn(GrammarType.DEFAULT);

    Rule rule = createMock(Rule.class);
    expect(rule.eContainer()).andReturn(grammar);

    LexerCommandArg ref = createMock(LexerCommandArg.class);

    EStructuralFeature feature = createMock(EStructuralFeature.class);

    LexerCommandExpr args = createMock(LexerCommandExpr.class);
    expect(args.getRef()).andReturn(ref);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getArgs()).andReturn(args);
    expect(command.eContainer()).andReturn(rule);

    INode node = createMock(INode.class);
    expect(node.getText()).andReturn("HIDDEN");

    PowerMock.mockStatic(NodeModelUtils.class);
    expect(NodeModelUtils.findNodesForFeature(args, LangPackage.Literals.LEXER_COMMAND_EXPR__REF))
        .andReturn(Arrays.asList(node));

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "warning");

    Object[] mocks = {grammar, rule, command, args, ref, feature, validator, node };
    PowerMock.replay(NodeModelUtils.class);

    replay(mocks);

    validator.commandWithUnrecognizedConstantValue(command);

    verify(mocks);
    PowerMock.verify(NodeModelUtils.class);
  }

  @Test
  public void warnCommandWithAnythingElse() throws Exception {
    Grammar grammar = createMock(Grammar.class);
    expect(grammar.getType()).andReturn(GrammarType.DEFAULT);

    Rule rule = createMock(Rule.class);
    expect(rule.eContainer()).andReturn(grammar);
    expect(rule.getName()).andReturn("RULE");

    LexerCommandArg ref = createMock(LexerCommandArg.class);

    EStructuralFeature feature = createMock(EStructuralFeature.class);

    EClass eClass = createMock(EClass.class);
    expect(eClass.getEStructuralFeature("ref")).andReturn(feature);

    LexerCommandExpr args = createMock(LexerCommandExpr.class);
    expect(args.getRef()).andReturn(ref);
    expect(args.eClass()).andReturn(eClass);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getArgs()).andReturn(args);
    expect(command.eContainer()).andReturn(rule);

    INode node = createMock(INode.class);
    expect(node.getText()).andReturn("Something");

    PowerMock.mockStatic(NodeModelUtils.class);
    expect(NodeModelUtils.findNodesForFeature(args, LangPackage.Literals.LEXER_COMMAND_EXPR__REF))
        .andReturn(Arrays.asList(node));

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "warning");

    PowerMock.expectPrivate(validator, "warning",
        "rule 'RULE' contains a lexer command with an unrecognized " +
            "constant value; lexer interpreters may produce incorrect output",
        args, feature);

    Object[] mocks = {grammar, rule, command, args, ref, eClass, feature, validator, node };
    PowerMock.replay(NodeModelUtils.class);

    replay(mocks);

    validator.commandWithUnrecognizedConstantValue(command);

    verify(mocks);
    PowerMock.verify(NodeModelUtils.class);
  }

  @Test
  public void typeCommandWithoutArgumentMustFail() throws Exception {
    commandWithoutArgument("type");
  }

  @Test
  public void channelCommandWithoutArgumentMustFail() throws Exception {
    commandWithoutArgument("channel");
  }

  @Test
  public void modeCommandWithoutArgumentMustFail() throws Exception {
    commandWithoutArgument("mode");
  }

  @Test
  public void pushModeCommandWithoutArgumentMustFail() throws Exception {
    commandWithoutArgument("pushMode");
  }

  @Test
  public void skipCommandWithArgumentMustFail() throws Exception {
    commandWithArgument("skip");
  }

  @Test
  public void moreCommandWithArgumentMustFail() throws Exception {
    commandWithArgument("more");
  }

  @Test
  public void popModeCommandWithArgumentMustFail() throws Exception {
    commandWithArgument("popMode");
  }

  @Test
  public void validCommands() throws Exception {
    String[] commands = {"skip", "more", "popMode", "type", "channel", "mode", "pushMode" };
    for (String comamndName : commands) {
      LexerCommand command = createMock(LexerCommand.class);
      expect(command.getName()).andReturn(comamndName);

      Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "error");

      Object[] mocks = {command, validator };

      replay(mocks);

      validator.unsupported(command);

      verify(mocks);
    }
  }

  private void commandWithoutArgument(final String commandName) throws Exception {
    EStructuralFeature feature = createMock(EStructuralFeature.class);

    EClass eClass = createMock(EClass.class);
    expect(eClass.getEStructuralFeature("name")).andReturn(feature);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getName()).andReturn(commandName);
    expect(command.getArgs()).andReturn(null);
    expect(command.eClass()).andReturn(eClass);

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "error");

    PowerMock.expectPrivate(validator, "error", "missing argument for lexer command '"
        + commandName + "' ", command, feature);

    Object[] mocks = {command, eClass, feature, validator };

    replay(mocks);

    validator.missingArgument(command);

    verify(mocks);
  }

  private void commandWithArgument(final String commandName) throws Exception {
    EStructuralFeature feature = createMock(EStructuralFeature.class);

    LexerCommandExpr args = createMock(LexerCommandExpr.class);

    EClass eClass = createMock(EClass.class);
    expect(eClass.getEStructuralFeature("name")).andReturn(feature);

    LexerCommand command = createMock(LexerCommand.class);
    expect(command.getName()).andReturn(commandName);
    expect(command.getArgs()).andReturn(args);
    expect(command.eClass()).andReturn(eClass);

    Antlr4Validator validator = PowerMock.createPartialMock(Antlr4Validator.class, "error");

    PowerMock.expectPrivate(validator, "error", "lexer command '" + commandName
        + "' does not take any arguments", command, feature);

    Object[] mocks = {command, eClass, feature, args, validator };

    replay(mocks);

    validator.noArgument(command);

    verify(mocks);
  }
}
