/*
 * 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;

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

import java.util.ArrayList;
import java.util.Collections;

import org.apache.avro.Schema.Field;

/**
 * Schemas used by other tests in this package. Therefore mostly package
 * protected.
 */
public class TestSchemas {

  static final Schema NULL_SCHEMA = Schema.create(Schema.Type.NULL);
  static final Schema BOOLEAN_SCHEMA = Schema.create(Schema.Type.BOOLEAN);
  static final Schema INT_SCHEMA = Schema.create(Schema.Type.INT);
  static final Schema LONG_SCHEMA = Schema.create(Schema.Type.LONG);
  static final Schema FLOAT_SCHEMA = Schema.create(Schema.Type.FLOAT);
  static final Schema DOUBLE_SCHEMA = Schema.create(Schema.Type.DOUBLE);
  static final Schema STRING_SCHEMA = Schema.create(Schema.Type.STRING);
  static final Schema BYTES_SCHEMA = Schema.create(Schema.Type.BYTES);

  static final Schema INT_ARRAY_SCHEMA = Schema.createArray(INT_SCHEMA);
  static final Schema LONG_ARRAY_SCHEMA = Schema.createArray(LONG_SCHEMA);
  static final Schema STRING_ARRAY_SCHEMA = Schema.createArray(STRING_SCHEMA);

  static final Schema INT_MAP_SCHEMA = Schema.createMap(INT_SCHEMA);
  static final Schema LONG_MAP_SCHEMA = Schema.createMap(LONG_SCHEMA);
  static final Schema STRING_MAP_SCHEMA = Schema.createMap(STRING_SCHEMA);

  static final Schema ENUM1_AB_SCHEMA = Schema.createEnum("Enum1", null, null, list("A", "B"));
  static final Schema ENUM1_AB_SCHEMA_DEFAULT = Schema.createEnum("Enum1", null, null, list("A", "B"), "A");
  public static final Schema ENUM1_AB_SCHEMA_NAMESPACE_1 = Schema.createEnum("Enum1", null, "namespace1",
      list("A", "B"));
  public static final Schema ENUM1_AB_SCHEMA_NAMESPACE_2 = Schema.createEnum("Enum1", null, "namespace2",
      list("A", "B"));
  static final Schema ENUM1_ABC_SCHEMA = Schema.createEnum("Enum1", null, null, list("A", "B", "C"));
  static final Schema ENUM1_BC_SCHEMA = Schema.createEnum("Enum1", null, null, list("B", "C"));
  static final Schema ENUM2_AB_SCHEMA = Schema.createEnum("Enum2", null, null, list("A", "B"));
  static final Schema ENUM_ABC_ENUM_DEFAULT_A_SCHEMA = Schema.createEnum("Enum", null, null, list("A", "B", "C"), "A");
  static final Schema ENUM_AB_ENUM_DEFAULT_A_SCHEMA = Schema.createEnum("Enum", null, null, list("A", "B"), "A");
  static final Schema ENUM_ABC_ENUM_DEFAULT_A_RECORD = Schema.createRecord("Record", null, null, false);
  static final Schema ENUM_AB_ENUM_DEFAULT_A_RECORD = Schema.createRecord("Record", null, null, false);
  static final Schema ENUM_ABC_FIELD_DEFAULT_B_ENUM_DEFAULT_A_RECORD = Schema.createRecord("Record", null, null, false);
  static final Schema ENUM_AB_FIELD_DEFAULT_A_ENUM_DEFAULT_B_RECORD = Schema.createRecord("Record", null, null, false);
  static {
    ENUM_ABC_ENUM_DEFAULT_A_RECORD.setFields(
        list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, list("A", "B", "C"), "A"), null, null)));
    ENUM_AB_ENUM_DEFAULT_A_RECORD.setFields(
        list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, list("A", "B"), "A"), null, null)));
    ENUM_ABC_FIELD_DEFAULT_B_ENUM_DEFAULT_A_RECORD.setFields(
        list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, list("A", "B", "C"), "A"), null, "B")));
    ENUM_AB_FIELD_DEFAULT_A_ENUM_DEFAULT_B_RECORD.setFields(
        list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, list("A", "B"), "B"), null, "A")));
  }

  static final Schema EMPTY_UNION_SCHEMA = Schema.createUnion(new ArrayList<>());
  static final Schema NULL_UNION_SCHEMA = Schema.createUnion(list(NULL_SCHEMA));
  static final Schema INT_UNION_SCHEMA = Schema.createUnion(list(INT_SCHEMA));
  static final Schema LONG_UNION_SCHEMA = Schema.createUnion(list(LONG_SCHEMA));
  static final Schema FLOAT_UNION_SCHEMA = Schema.createUnion(list(FLOAT_SCHEMA));
  static final Schema DOUBLE_UNION_SCHEMA = Schema.createUnion(list(DOUBLE_SCHEMA));
  static final Schema STRING_UNION_SCHEMA = Schema.createUnion(list(STRING_SCHEMA));
  static final Schema BYTES_UNION_SCHEMA = Schema.createUnion(list(BYTES_SCHEMA));
  static final Schema INT_STRING_UNION_SCHEMA = Schema.createUnion(list(INT_SCHEMA, STRING_SCHEMA));
  static final Schema STRING_INT_UNION_SCHEMA = Schema.createUnion(list(STRING_SCHEMA, INT_SCHEMA));
  static final Schema INT_FLOAT_UNION_SCHEMA = Schema.createUnion(list(INT_SCHEMA, FLOAT_SCHEMA));
  static final Schema INT_LONG_UNION_SCHEMA = Schema.createUnion(list(INT_SCHEMA, LONG_SCHEMA));
  static final Schema INT_LONG_FLOAT_DOUBLE_UNION_SCHEMA = Schema
      .createUnion(list(INT_SCHEMA, LONG_SCHEMA, FLOAT_SCHEMA, DOUBLE_SCHEMA));

  static final Schema NULL_INT_ARRAY_UNION_SCHEMA = Schema.createUnion(list(NULL_SCHEMA, INT_ARRAY_SCHEMA));
  static final Schema NULL_INT_MAP_UNION_SCHEMA = Schema.createUnion(list(NULL_SCHEMA, INT_MAP_SCHEMA));

  // Non recursive records:
  static final Schema EMPTY_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema EMPTY_RECORD2 = Schema.createRecord("Record2", null, null, false);
  static final Schema A_INT_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_LONG_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_INT_B_INT_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_RECORD1 = // DTYPE means TYPE with default value
      Schema.createRecord("Record1", null, null, false);
  static final Schema A_INT_B_DINT_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_B_DINT_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_B_DFIXED_4_BYTES_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_B_DFIXED_8_BYTES_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_B_DINT_STRING_UNION_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_B_DINT_UNION_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_B_DENUM_1_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema A_DINT_B_DENUM_2_RECORD1 = Schema.createRecord("Record1", null, null, false);

  static final Schema FIXED_4_BYTES = Schema.createFixed("Fixed", null, null, 4);
  static final Schema FIXED_8_BYTES = Schema.createFixed("Fixed", null, null, 8);

  static final Schema NS_RECORD1 = Schema.createRecord("Record1", null, null, false);
  static final Schema NS_RECORD2 = Schema.createRecord("Record1", null, null, false);
  static final Schema NS_INNER_RECORD1 = Schema.createRecord("InnerRecord1", null, "ns1", false);
  static final Schema NS_INNER_RECORD2 = Schema.createRecord("InnerRecord1", null, "ns2", false);

  static final Schema WITHOUT_NS = Schema.createRecord("Record", null, null, false);
  static final Schema WITH_NS = Schema.createRecord("ns.Record", null, null, false);

  static {
    EMPTY_RECORD1.setFields(Collections.emptyList());
    EMPTY_RECORD2.setFields(Collections.emptyList());
    A_INT_RECORD1.setFields(list(new Field("a", INT_SCHEMA, null, null)));
    A_LONG_RECORD1.setFields(list(new Field("a", LONG_SCHEMA, null, null)));
    A_INT_B_INT_RECORD1.setFields(list(new Field("a", INT_SCHEMA, null, null), new Field("b", INT_SCHEMA, null, null)));
    A_DINT_RECORD1.setFields(list(new Field("a", INT_SCHEMA, null, 0)));
    A_INT_B_DINT_RECORD1.setFields(list(new Field("a", INT_SCHEMA, null, null), new Field("b", INT_SCHEMA, null, 0)));
    A_DINT_B_DINT_RECORD1.setFields(list(new Field("a", INT_SCHEMA, null, 0), new Field("b", INT_SCHEMA, null, 0)));
    A_DINT_B_DFIXED_4_BYTES_RECORD1
        .setFields(list(new Field("a", INT_SCHEMA, null, 0), new Field("b", FIXED_4_BYTES, null, null)));
    A_DINT_B_DFIXED_8_BYTES_RECORD1
        .setFields(list(new Field("a", INT_SCHEMA, null, 0), new Field("b", FIXED_8_BYTES, null, null)));
    A_DINT_B_DINT_STRING_UNION_RECORD1
        .setFields(list(new Field("a", INT_SCHEMA, null, 0), new Field("b", INT_STRING_UNION_SCHEMA, null, 0)));
    A_DINT_B_DINT_UNION_RECORD1
        .setFields(list(new Field("a", INT_SCHEMA, null, 0), new Field("b", INT_UNION_SCHEMA, null, 0)));
    A_DINT_B_DENUM_1_RECORD1
        .setFields(list(new Field("a", INT_SCHEMA, null, 0), new Field("b", ENUM1_AB_SCHEMA, null, null)));
    A_DINT_B_DENUM_2_RECORD1
        .setFields(list(new Field("a", INT_SCHEMA, null, 0), new Field("b", ENUM2_AB_SCHEMA, null, null)));

    NS_INNER_RECORD1.setFields(list(new Schema.Field("a", INT_SCHEMA)));
    NS_INNER_RECORD2.setFields(list(new Schema.Field("a", INT_SCHEMA)));

    NS_RECORD1
        .setFields(list(new Schema.Field("f1", Schema.createUnion(NULL_SCHEMA, Schema.createArray(NS_INNER_RECORD1)))));
    NS_RECORD2
        .setFields(list(new Schema.Field("f1", Schema.createUnion(NULL_SCHEMA, Schema.createArray(NS_INNER_RECORD2)))));

    WITH_NS.setFields(list(new Field("f1", INT_SCHEMA, null, null)));
    WITHOUT_NS.setFields(list(new Field("f1", INT_SCHEMA, null, null)));
  }

  // Recursive records
  static final Schema INT_LIST_RECORD = Schema.createRecord("List", null, null, false);
  static final Schema LONG_LIST_RECORD = Schema.createRecord("List", null, null, false);
  static {
    INT_LIST_RECORD
        .setFields(list(new Field("head", INT_SCHEMA, null, null), new Field("tail", INT_LIST_RECORD, null, null)));
    LONG_LIST_RECORD
        .setFields(list(new Field("head", LONG_SCHEMA, null, null), new Field("tail", LONG_LIST_RECORD, null, null)));
  }

  // -----------------------------------------------------------------------------------------------

  /** Reader/writer schema pair. */
  static final class ReaderWriter {
    private final Schema mReader;
    private final Schema mWriter;

    public ReaderWriter(final Schema reader, final Schema writer) {
      mReader = reader;
      mWriter = writer;
    }

    public Schema getReader() {
      return mReader;
    }

    public Schema getWriter() {
      return mWriter;
    }
  }

  /** Borrowed from the Guava library. */
  static <E> ArrayList<E> list(E... elements) {
    final ArrayList<E> list = new ArrayList<>();
    Collections.addAll(list, elements);
    return list;
  }

  static void assertSchemaContains(Schema schemaSubset, Schema original) {
    String subset = schemaSubset.toString(false);
    String whole = original.toString(false);
    assertTrue(whole.contains(subset), String.format("Subset '%s' not found in '%s'", subset, whole));
  }

}
