/**
 * 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
 *
 *     http://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 java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;

import org.apache.avro.Schema;
import org.apache.avro.io.TestValidatingIO.Encoding;
import org.junit.Test;
import org.junit.runners.Parameterized;
import org.junit.runner.RunWith;

@RunWith(Parameterized.class)
public class TestResolvingIO {

  protected final Encoding eEnc;
  protected final int iSkipL;
  protected final String sJsWrtSchm;
  protected final String sWrtCls;
  protected final String sJsRdrSchm;
  protected final String sRdrCls;

  public TestResolvingIO (Encoding encoding,
      int skipLevel, String jsonWriterSchema,
      String writerCalls,
      String jsonReaderSchema, String readerCalls
  ) {
    this.eEnc = encoding;
    this.iSkipL = skipLevel;
    this.sJsWrtSchm = jsonWriterSchema;
    this.sWrtCls = writerCalls;
    this.sJsRdrSchm = jsonReaderSchema;
    this.sRdrCls = readerCalls;
  }
  
  @Test
  public void testIdentical() throws IOException {
    performTest(eEnc, iSkipL, sJsWrtSchm, sWrtCls, sJsWrtSchm, sWrtCls);
  }

  private static final int COUNT = 10;

  @Test
  public void testCompatible() throws IOException {
    performTest(eEnc, iSkipL, sJsWrtSchm, sWrtCls, sJsRdrSchm, sRdrCls);
  }

  private void performTest(Encoding encoding,
      int skipLevel, String jsonWriterSchema,
      String writerCalls, 
      String jsonReaderSchema, String readerCalls)
  throws IOException {
    for (int i = 0; i < COUNT; i++) {
      testOnce(jsonWriterSchema, writerCalls,
          jsonReaderSchema, readerCalls, encoding, skipLevel);
    }
  }
  
  private void testOnce(String jsonWriterSchema,
      String writerCalls,
      String jsonReaderSchema,
      String readerCalls,
      Encoding encoding,
      int skipLevel) throws IOException {
    Object[] values = TestValidatingIO.randomValues(writerCalls);
    Object[] expected = TestValidatingIO.randomValues(readerCalls);
    
    Schema writerSchema = new Schema.Parser().parse(jsonWriterSchema);
    byte[] bytes = TestValidatingIO.make(writerSchema, writerCalls,
        values, encoding);
    Schema readerSchema = new Schema.Parser().parse(jsonReaderSchema);
    TestValidatingIO.print(encoding, skipLevel, writerSchema, readerSchema, values, expected);
    check(writerSchema, readerSchema, bytes, readerCalls,
        expected,
        encoding, skipLevel);
  }

  static void check(Schema wsc, Schema rsc, byte[] bytes,
      String calls, Object[] values, Encoding encoding,
      int skipLevel)
      throws IOException {
    // TestValidatingIO.dump(bytes);
    // System.out.println(new String(bytes, "UTF-8"));
    Decoder bvi = null;
    switch (encoding) {
    case BINARY:
    case BLOCKING_BINARY:
      bvi = DecoderFactory.get().binaryDecoder(bytes, null);
      break;
    case JSON:
      InputStream in = new ByteArrayInputStream(bytes);
      bvi = new JsonDecoder(wsc, in);
      break;
    }
    Decoder vi = new ResolvingDecoder(wsc, rsc, bvi);
    TestValidatingIO.check(vi, calls, values, skipLevel);
  }
  
  @Parameterized.Parameters
  public static Collection<Object[]> data2() {
    return Arrays.asList(TestValidatingIO.convertTo2dArray(encodings, skipLevels, testSchemas()));
  }

  static Object[][] encodings = new Object[][] { { Encoding.BINARY },
          { Encoding.BLOCKING_BINARY }, { Encoding.JSON } };
  static Object[][] skipLevels =
    new Object[][] { { -1 }, { 0 }, { 1 }, { 2 }  };
  private static Object[][] testSchemas() {
    // The mnemonics are the same as {@link TestValidatingIO#testSchemas}
    return new Object[][] {
        { "\"int\"", "I", "\"float\"", "F" },
        { "\"int\"", "I", "\"double\"", "D" },
        { "\"int\"", "I", "\"long\"", "L" },
        { "\"long\"", "L", "\"float\"", "F" },
        { "\"long\"", "L", "\"double\"", "D" },
        { "\"float\"", "F", "\"double\"", "D" },

        { "{\"type\":\"array\", \"items\": \"int\"}", "[]",
          "{\"type\":\"array\", \"items\": \"long\"}", "[]", },
        { "{\"type\":\"array\", \"items\": \"int\"}", "[]",
          "{\"type\":\"array\", \"items\": \"double\"}", "[]" },
        { "{\"type\":\"array\", \"items\": \"long\"}", "[]",
          "{\"type\":\"array\", \"items\": \"double\"}", "[]" },
        { "{\"type\":\"array\", \"items\": \"float\"}", "[]",
          "{\"type\":\"array\", \"items\": \"double\"}", "[]" },

        { "{\"type\":\"array\", \"items\": \"int\"}", "[c1sI]",
          "{\"type\":\"array\", \"items\": \"long\"}", "[c1sL]" },
        { "{\"type\":\"array\", \"items\": \"int\"}", "[c1sI]",
          "{\"type\":\"array\", \"items\": \"double\"}", "[c1sD]" },
        { "{\"type\":\"array\", \"items\": \"long\"}", "[c1sL]",
          "{\"type\":\"array\", \"items\": \"double\"}", "[c1sD]" },
        { "{\"type\":\"array\", \"items\": \"float\"}", "[c1sF]",
          "{\"type\":\"array\", \"items\": \"double\"}", "[c1sD]" },

        { "{\"type\":\"map\", \"values\": \"int\"}", "{}",
          "{\"type\":\"map\", \"values\": \"long\"}", "{}" },
        { "{\"type\":\"map\", \"values\": \"int\"}", "{}",
          "{\"type\":\"map\", \"values\": \"double\"}", "{}" },
        { "{\"type\":\"map\", \"values\": \"long\"}", "{}",
          "{\"type\":\"map\", \"values\": \"double\"}", "{}" },
        { "{\"type\":\"map\", \"values\": \"float\"}", "{}",
          "{\"type\":\"map\", \"values\": \"double\"}", "{}" },

        { "{\"type\":\"map\", \"values\": \"int\"}", "{c1sK5I}",
          "{\"type\":\"map\", \"values\": \"long\"}", "{c1sK5L}" },
        { "{\"type\":\"map\", \"values\": \"int\"}", "{c1sK5I}",
          "{\"type\":\"map\", \"values\": \"double\"}", "{c1sK5D}" },
        { "{\"type\":\"map\", \"values\": \"long\"}", "{c1sK5L}",
          "{\"type\":\"map\", \"values\": \"double\"}", "{c1sK5D}" },
        { "{\"type\":\"map\", \"values\": \"float\"}", "{c1sK5F}",
          "{\"type\":\"map\", \"values\": \"double\"}", "{c1sK5D}" },

        { "{\"type\":\"record\",\"name\":\"r\",\"fields\":["
          + "{\"name\":\"f\", \"type\":\"int\"}]}", "I",
          "{\"type\":\"record\",\"name\":\"r\",\"fields\":["
          + "{\"name\":\"f\", \"type\":\"long\"}]}", "L" },
        { "{\"type\":\"record\",\"name\":\"r\",\"fields\":["
          + "{\"name\":\"f\", \"type\":\"int\"}]}", "I",
          "{\"type\":\"record\",\"name\":\"r\",\"fields\":["
          + "{\"name\":\"f\", \"type\":\"double\"}]}", "D" },

        // multi-field record with promotions
        { "{\"type\":\"record\",\"name\":\"r\",\"fields\":["
          + "{\"name\":\"f0\", \"type\":\"boolean\"},"
          + "{\"name\":\"f1\", \"type\":\"int\"},"
          + "{\"name\":\"f2\", \"type\":\"float\"},"
          + "{\"name\":\"f3\", \"type\":\"bytes\"},"
          + "{\"name\":\"f4\", \"type\":\"string\"}]}", "BIFbS",
          "{\"type\":\"record\",\"name\":\"r\",\"fields\":["
          + "{\"name\":\"f0\", \"type\":\"boolean\"},"
          + "{\"name\":\"f1\", \"type\":\"long\"},"
          + "{\"name\":\"f2\", \"type\":\"double\"},"
          + "{\"name\":\"f3\", \"type\":\"string\"},"
          + "{\"name\":\"f4\", \"type\":\"bytes\"}]}", "BLDSb" },

        { "[\"int\"]", "U0I",
              "[\"long\"]", "U0L" },
        { "[\"int\"]", "U0I",
              "[\"double\"]", "U0D" },
        { "[\"long\"]", "U0L",
              "[\"double\"]", "U0D" },
        { "[\"float\"]", "U0F",
              "[\"double\"]", "U0D" },

        { "\"int\"", "I", "[\"int\"]", "U0I" },

        { "[\"int\"]", "U0I", "\"int\"", "I" },
        { "[\"int\"]", "U0I", "\"long\"", "L" },

        { "[\"boolean\", \"int\"]", "U1I",
              "[\"boolean\", \"long\"]", "U1L" },
        { "[\"boolean\", \"int\"]", "U1I",
              "[\"long\", \"boolean\"]", "U0L" },
    };
  }
}
