/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.avro.io;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.apache.avro.AvroTypeException;
import org.apache.avro.Schema;
import org.apache.avro.SchemaBuilder;
import org.apache.avro.SchemaParser;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericRecord;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class TestJsonDecoder {

  @Test
  void testInt() throws Exception {
    checkNumeric("int", 1);
  }

  @Test
  void testLong() throws Exception {
    checkNumeric("long", 1L);
  }

  @Test
  void testFloat() throws Exception {
    checkNumeric("float", 1.0F);
  }

  @Test
  void testDouble() throws Exception {
    checkNumeric("double", 1.0);
  }

  private void checkNumeric(String type, Object value) throws Exception {
    String def = "{\"type\":\"record\",\"name\":\"X\",\"fields\":" + "[{\"type\":\"" + type + "\",\"name\":\"n\"}]}";
    Schema schema = SchemaParser.parseSingle(def);
    DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);

    String[] records = { "{\"n\":1}", "{\"n\":1.0}" };

    for (String record : records) {
      Decoder decoder = DecoderFactory.get().jsonDecoder(schema, record);
      GenericRecord r = reader.read(null, decoder);
      assertEquals(value, r.get("n"));
    }
  }

  // Ensure that even if the order of fields in JSON is different from the order
  // in schema,
  // it works.
  @Test
  void reorderFields() throws Exception {
    String w = "{\"type\":\"record\",\"name\":\"R\",\"fields\":" + "[{\"type\":\"long\",\"name\":\"l\"},"
        + "{\"type\":{\"type\":\"array\",\"items\":\"int\"},\"name\":\"a\"}" + "]}";
    Schema ws = SchemaParser.parseSingle(w);
    DecoderFactory df = DecoderFactory.get();
    String data = "{\"a\":[1,2],\"l\":100}{\"l\": 200, \"a\":[1,2]}";
    JsonDecoder in = df.jsonDecoder(ws, data);
    assertEquals(100, in.readLong());
    in.skipArray();
    assertEquals(200, in.readLong());
    in.skipArray();
  }

  @Test
  void testIntWithError() throws IOException {
    Schema schema = SchemaBuilder.builder("test").record("example").fields().requiredInt("id").endRecord();
    String record = "{ \"id\": -1.2 }";

    GenericDatumReader<GenericRecord> reader = new GenericDatumReader<>(schema, schema);
    JsonDecoder decoder = DecoderFactory.get().jsonDecoder(schema, record);
    Assertions.assertThrows(AvroTypeException.class, () -> reader.read(null, decoder));
  }

  @Test
  void testIeee754SpecialCases() throws IOException {
    String def = "{\"type\":\"record\",\"name\":\"X\",\"fields\": [" + "{\"type\":\"float\",\"name\":\"nanFloat\"},"
        + "{\"type\":\"float\",\"name\":\"infinityFloat\"},"
        + "{\"type\":\"float\",\"name\":\"negativeInfinityFloat\"}," + "{\"type\":\"double\",\"name\":\"nanDouble\"},"
        + "{\"type\":\"double\",\"name\":\"infinityDouble\"},"
        + "{\"type\":\"double\",\"name\":\"negativeInfinityDouble\"}" + "]}";
    Schema schema = SchemaParser.parseSingle(def);
    DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);

    String record = "{\"nanFloat\":\"NaN\", \"infinityFloat\":\"Infinity\", \"negativeInfinityFloat\":\"-Infinity\", "
        + "\"nanDouble\":\"NaN\", \"infinityDouble\":\"Infinity\", \"negativeInfinityDouble\":\"-Infinity\"}";
    Decoder decoder = DecoderFactory.get().jsonDecoder(schema, record);
    GenericRecord r = reader.read(null, decoder);
    assertEquals(Float.NaN, r.get("nanFloat"));
    assertEquals(Float.POSITIVE_INFINITY, r.get("infinityFloat"));
    assertEquals(Float.NEGATIVE_INFINITY, r.get("negativeInfinityFloat"));
    assertEquals(Double.NaN, r.get("nanDouble"));
    assertEquals(Double.POSITIVE_INFINITY, r.get("infinityDouble"));
    assertEquals(Double.NEGATIVE_INFINITY, r.get("negativeInfinityDouble"));
  }
}
