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

/*
 * Copyright 2017 The Apache Software Foundation.
 *
 * Licensed 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.util;

import org.apache.avro.Schema;
import org.apache.avro.SchemaParser;
import org.junit.Assert;
import org.junit.Test;

public class TestSchemas {

  private static class TestVisitor implements SchemaVisitor<String> {
    StringBuilder sb = new StringBuilder();

    @Override
    public SchemaVisitorAction visitTerminal(Schema terminal) {
      sb.append(terminal);
      return SchemaVisitorAction.CONTINUE;
    }

    @Override
    public SchemaVisitorAction visitNonTerminal(Schema nonTerminal) {
      String n = nonTerminal.getName();
      sb.append(n).append('.');
      if (n.startsWith("t")) {
        return SchemaVisitorAction.TERMINATE;
      } else if (n.startsWith("ss")) {
        return SchemaVisitorAction.SKIP_SIBLINGS;
      } else if (n.startsWith("st")) {
        return SchemaVisitorAction.SKIP_SUBTREE;
      } else {
        return SchemaVisitorAction.CONTINUE;
      }
    }

    @Override
    public SchemaVisitorAction afterVisitNonTerminal(Schema nonTerminal) {
      sb.append("!");
      String n = nonTerminal.getName();
      if (n.startsWith("ct")) {
        return SchemaVisitorAction.TERMINATE;
      } else if (n.startsWith("css")) {
        return SchemaVisitorAction.SKIP_SIBLINGS;
      } else if (n.startsWith("cst")) {
        return SchemaVisitorAction.SKIP_SUBTREE;
      } else {
        return SchemaVisitorAction.CONTINUE;
      }
    }

    @Override
    public String get() {
      return sb.toString();
    }
  }

  @Test
  public void testVisit1() {
    String s1 = "{\"type\": \"record\", \"name\": \"t1\", \"fields\": [{\"name\": \"f1\", \"type\": \"int\"}]}";
    Assert.assertEquals("t1.", Schemas.visit(SchemaParser.parseSingle(s1), new TestVisitor()));
  }

  @Test
  public void testVisit2() {
    String s2 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": [{\"name\": \"f1\", \"type\": \"int\"}]}";
    Assert.assertEquals("c1.\"int\"!", Schemas.visit(SchemaParser.parseSingle(s2), new TestVisitor()));

  }

  @Test
  public void testVisit3() {
    String s3 = "{\"type\": \"record\", \"name\": \"ss1\", \"fields\": [{\"name\": \"f1\", \"type\": \"int\"}]}";
    Assert.assertEquals("ss1.", Schemas.visit(SchemaParser.parseSingle(s3), new TestVisitor()));

  }

  @Test
  public void testVisit4() {
    String s4 = "{\"type\": \"record\", \"name\": \"st1\", \"fields\": [{\"name\": \"f1\", \"type\": \"int\"}]}";
    Assert.assertEquals("st1.!", Schemas.visit(SchemaParser.parseSingle(s4), new TestVisitor()));

  }

  @Test
  public void testVisit5() {
    String s5 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"c2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"}]}}," + "{\"name\": \"f2\", \"type\": \"long\"}" + "]}";
    Assert.assertEquals("c1.c2.\"int\"!\"long\"!", Schemas.visit(SchemaParser.parseSingle(s5), new TestVisitor()));

  }

  @Test
  public void testVisit6() {
    String s6 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"ss2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"}]}}," + "{\"name\": \"f2\", \"type\": \"long\"}" + "]}";
    Assert.assertEquals("c1.ss2.!", Schemas.visit(SchemaParser.parseSingle(s6), new TestVisitor()));

  }

  @Test
  public void testVisit7() {
    String s7 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"css2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"}]}}," + "{\"name\": \"f2\", \"type\": \"long\"}" + "]}";
    Assert.assertEquals("c1.css2.\"int\"!!", Schemas.visit(SchemaParser.parseSingle(s7), new TestVisitor()));
  }

  @Test(expected = UnsupportedOperationException.class)
  public void testVisit8() {
    String s8 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"cst2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"}]}}," + "{\"name\": \"f2\", \"type\": \"int\"}" + "]}";
    Schemas.visit(SchemaParser.parseSingle(s8), new TestVisitor());
  }

  @Test
  public void testVisit9() {
    String s9 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"ct2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"}]}}," + "{\"name\": \"f2\", \"type\": \"long\"}" + "]}";
    Assert.assertEquals("c1.ct2.\"int\"!", Schemas.visit(SchemaParser.parseSingle(s9), new TestVisitor()));
  }

  @Test(expected = UnsupportedOperationException.class)
  public void testVisit10() {
    String s10 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"ct2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"}]}}," + "{\"name\": \"f2\", \"type\": \"int\"}" + "]}";
    Schemas.visit(SchemaParser.parseSingle(s10), new TestVisitor() {
      @Override
      public SchemaVisitorAction visitTerminal(Schema terminal) {
        return SchemaVisitorAction.SKIP_SUBTREE;
      }
    });
  }

  @Test
  public void testVisit11() {
    String s11 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"c2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"},{\"name\": \"f12\", \"type\": \"double\"}" + "]}},"
        + "{\"name\": \"f2\", \"type\": \"long\"}" + "]}";
    Assert.assertEquals("c1.c2.\"int\".!\"long\".!", Schemas.visit(SchemaParser.parseSingle(s11), new TestVisitor() {
      @Override
      public SchemaVisitorAction visitTerminal(Schema terminal) {
        sb.append(terminal).append('.');
        return SchemaVisitorAction.SKIP_SIBLINGS;
      }
    }));
  }

  @Test
  public void testVisit12() {
    String s12 = "{\"type\": \"record\", \"name\": \"c1\", \"fields\": ["
        + "{\"name\": \"f1\", \"type\": {\"type\": \"record\", \"name\": \"ct2\", \"fields\": "
        + "[{\"name\": \"f11\", \"type\": \"int\"}]}}," + "{\"name\": \"f2\", \"type\": \"long\"}" + "]}";
    Assert.assertEquals("c1.ct2.\"int\".", Schemas.visit(SchemaParser.parseSingle(s12), new TestVisitor() {
      @Override
      public SchemaVisitorAction visitTerminal(Schema terminal) {
        sb.append(terminal).append('.');
        return SchemaVisitorAction.TERMINATE;
      }
    }));
  }

  @Test
  public void testVisit13() {
    String s12 = "{\"type\": \"int\"}";
    Assert.assertEquals("\"int\".", Schemas.visit(SchemaParser.parseSingle(s12), new TestVisitor() {
      @Override
      public SchemaVisitorAction visitTerminal(Schema terminal) {
        sb.append(terminal).append('.');
        return SchemaVisitorAction.SKIP_SIBLINGS;
      }
    }));
  }
}
