public final class ProcessorChainTest {
  private static final MessageBundle BUNDLE =
      MessageBundles.getBundle(JsonSchemaCoreMessageBundle.class);

  @Test
  public void cannotInitiateWithNullProcessor() {
    try {
      ProcessorChain.startWith(null);
      fail("No exception thrown!!");
    } catch (NullPointerException e) {
      assertEquals(e.getMessage(), BUNDLE.getMessage("processing.nullProcessor"));
    }
  }

  @Test
  public void cannotChainWithNullProcessor() {
    @SuppressWarnings("unchecked")
    final Processor<MessageProvider, MessageProvider> p = mock(Processor.class);
    try {
      ProcessorChain.startWith(p).chainWith(null);
      fail("No exception thrown!!");
    } catch (NullPointerException e) {
      assertEquals(e.getMessage(), BUNDLE.getMessage("processing.nullProcessor"));
    }
  }

  @Test
  public void failingOnErrorExitsEarly() throws ProcessingException {
    @SuppressWarnings("unchecked")
    final Processor<MessageProvider, MessageProvider> p1 = mock(Processor.class);
    @SuppressWarnings("unchecked")
    final Processor<MessageProvider, MessageProvider> p2 = mock(Processor.class);

    final Processor<MessageProvider, MessageProvider> processor =
        ProcessorChain.startWith(p1).failOnError().chainWith(p2).getProcessor();

    final MessageProvider input = mock(MessageProvider.class);
    final ProcessingReport report = new DummyReport(LogLevel.ERROR);

    try {
      processor.process(report, input);
      fail("No exception thrown!!");
    } catch (ProcessingException e) {
      assertMessage(e.getProcessingMessage())
          .hasMessage(BUNDLE.getMessage("processing.chainStopped"));
    }

    verify(p1).process(same(report), any(MessageProvider.class));
    verify(p2, never()).process(any(ProcessingReport.class), any(MessageProvider.class));
  }

  @Test
  public void noFailureDoesNotTriggerEarlyExit() throws ProcessingException {
    @SuppressWarnings("unchecked")
    final Processor<MessageProvider, MessageProvider> p1 = mock(Processor.class);
    @SuppressWarnings("unchecked")
    final Processor<MessageProvider, MessageProvider> p2 = mock(Processor.class);

    final Processor<MessageProvider, MessageProvider> processor =
        ProcessorChain.startWith(p1).failOnError().chainWith(p2).getProcessor();

    final MessageProvider input = mock(MessageProvider.class);
    final ProcessingReport report = new DummyReport(LogLevel.DEBUG);

    processor.process(report, input);

    verify(p1).process(same(report), any(MessageProvider.class));
    verify(p2).process(same(report), any(MessageProvider.class));
  }

  private static final class DummyReport extends AbstractProcessingReport {
    private DummyReport(final LogLevel currentLevel) throws ProcessingException {
      dispatch(new ProcessingMessage().setLogLevel(currentLevel));
    }

    @Override
    public void log(final LogLevel level, final ProcessingMessage message) {}
  }
}
public final class NotKeywordTest {
  private static final MessageBundle BUNDLE =
      MessageBundles.getBundle(JsonSchemaValidationBundle.class);
  private static final JsonNodeFactory FACTORY = JacksonUtils.nodeFactory();
  private static final ProcessingMessage MSG = new ProcessingMessage();

  private final KeywordValidator validator;

  private Processor<FullData, FullData> processor;
  private FullData data;
  private ProcessingReport report;

  public NotKeywordTest()
      throws IllegalAccessException, InvocationTargetException, InstantiationException {
    final Constructor<? extends KeywordValidator> constructor =
        DraftV4ValidatorDictionary.get().entries().get("not");
    validator = constructor == null ? null : constructor.newInstance(FACTORY.nullNode());
  }

  @BeforeMethod
  public void initEnvironment() {
    if (validator == null) return;

    final ObjectNode schema = FACTORY.objectNode();
    schema.put("not", FACTORY.objectNode());

    final SchemaTree tree = new CanonicalSchemaTree(schema);
    final JsonTree instance = new SimpleJsonTree(FACTORY.nullNode());
    data = new FullData(tree, instance);
    report = mock(ProcessingReport.class);
    when(report.getLogLevel()).thenReturn(LogLevel.DEBUG);
  }

  @Test
  public void keywordExists() {
    assertNotNull(validator, "no support for not??");
  }

  @Test(dependsOnMethods = "keywordExists")
  public void exceptionIsCorrectlyThrown() {
    processor = new DummyProcessor(WantedState.EX);

    try {
      validator.validate(processor, report, BUNDLE, data);
      fail("No exception thrown??");
    } catch (ProcessingException ignored) {
    }
  }

  @Test(dependsOnMethods = "keywordExists")
  public void successfulSubSchemaLeadsToFailure() throws ProcessingException {
    processor = new DummyProcessor(WantedState.OK);

    final ArgumentCaptor<ProcessingMessage> captor =
        ArgumentCaptor.forClass(ProcessingMessage.class);

    validator.validate(processor, report, BUNDLE, data);

    verify(report).error(captor.capture());

    final ProcessingMessage message = captor.getValue();

    assertMessage(message).isValidationError("not", BUNDLE.getMessage("err.draftv4.not.fail"));
  }

  @Test(dependsOnMethods = "keywordExists")
  public void failingSubSchemaLeadsToSuccess() throws ProcessingException {
    processor = new DummyProcessor(WantedState.KO);

    validator.validate(processor, report, BUNDLE, data);

    verify(report, never()).error(anyMessage());
  }

  private enum WantedState {
    OK {
      @Override
      void doIt(final ProcessingReport report) throws ProcessingException {}
    },
    KO {
      @Override
      void doIt(final ProcessingReport report) throws ProcessingException {
        report.error(MSG);
      }
    },
    EX {
      @Override
      void doIt(final ProcessingReport report) throws ProcessingException {
        throw new ProcessingException();
      }
    };

    abstract void doIt(final ProcessingReport report) throws ProcessingException;
  }

  private static final class DummyProcessor implements Processor<FullData, FullData> {
    private static final JsonPointer PTR = JsonPointer.of("not");

    private final WantedState wanted;

    private DummyProcessor(final WantedState wanted) {
      this.wanted = wanted;
    }

    @Override
    public FullData process(final ProcessingReport report, final FullData input)
        throws ProcessingException {
      assertEquals(input.getSchema().getPointer(), PTR);
      wanted.doIt(report);
      return input;
    }
  }
}
@Test
public abstract class AbstractKeywordValidatorTest {
  private static final MessageBundle BUNDLE =
      MessageBundles.getBundle(JsonSchemaValidationBundle.class);

  private final String keyword;
  private final KeywordValidatorFactory factory;
  private final JsonNode testNode;

  protected AbstractKeywordValidatorTest(
      final Dictionary<KeywordValidatorFactory> dict, final String prefix, final String keyword)
      throws IOException {
    this.keyword = keyword;
    factory = dict.entries().get(keyword);
    final String resourceName = String.format("/keyword/validators/%s/%s.json", prefix, keyword);
    testNode = JsonLoader.fromResource(resourceName);
  }

  @Test
  public final void keywordExists() {
    assertNotNull(factory, "no support for " + keyword + "??");
  }

  @DataProvider
  protected final Iterator<Object[]> getValueTests() {
    final List<Object[]> list = Lists.newArrayList();

    String msg;
    JsonNode msgNode, msgData, msgParams;

    for (final JsonNode node : testNode) {
      msgNode = node.get("message");
      msgData = node.get("msgData");
      msgParams = node.get("msgParams");
      msg = msgNode == null ? null : buildMessage(msgNode.textValue(), msgParams, msgData);
      list.add(
          new Object[] {
            node.get("digest"), node.get("data"), msg, node.get("valid").booleanValue(), msgData
          });
    }

    return list.iterator();
  }

  // Unfortunately, the suppress warning annotation is needed
  @Test(dataProvider = "getValueTests", dependsOnMethods = "keywordExists")
  public final void instancesAreValidatedCorrectly(
      final JsonNode digest,
      final JsonNode node,
      final String msg,
      final boolean valid,
      final ObjectNode msgData)
      throws IllegalAccessException, InvocationTargetException, InstantiationException,
          ProcessingException {
    // FIXME: dummy, but we have no choice
    final SchemaTree tree = new CanonicalSchemaTree(SchemaKey.anonymousKey(), digest);
    final JsonTree instance = new SimpleJsonTree(node);
    final FullData data = new FullData(tree, instance);

    final ProcessingReport report = mock(ProcessingReport.class);
    @SuppressWarnings("unchecked")
    final Processor<FullData, FullData> processor = mock(Processor.class);

    final KeywordValidator validator = factory.getKeywordValidator(digest);
    validator.validate(processor, report, BUNDLE, data);

    if (valid) {
      verify(report, never()).error(anyMessage());
      return;
    }

    final ArgumentCaptor<ProcessingMessage> captor =
        ArgumentCaptor.forClass(ProcessingMessage.class);

    verify(report).error(captor.capture());

    final ProcessingMessage message = captor.getValue();

    assertMessage(message).isValidationError(keyword, msg).hasContents(msgData);
  }

  private static String buildMessage(final String key, final JsonNode params, final JsonNode data) {
    final ProcessingMessage message = new ProcessingMessage().setMessage(BUNDLE.getMessage(key));
    if (params != null) {
      String name;
      JsonNode value;
      for (final JsonNode node : params) {
        name = node.textValue();
        value = data.get(name);
        message.putArgument(name, valueToArgument(value));
      }
    }
    return message.getMessage();
  }

  private static Object valueToArgument(final JsonNode value) {
    final NodeType type = NodeType.getNodeType(value);

    switch (type) {
      case STRING:
        return value.textValue();
      case INTEGER:
        return value.bigIntegerValue();
      case NUMBER:
      case NULL:
      case OBJECT:
      case ARRAY:
        return value;
      case BOOLEAN:
        return value.booleanValue();
        //            case ARRAY:
        //                final List<Object> list = Lists.newArrayList();
        //                for (final JsonNode element: value)
        //                    list.add(valueToArgument(element));
        //                return list;
      default:
        throw new UnsupportedOperationException();
    }
  }
}