content: import java.io.File;
  import java.util.Arrays;
  import java.util.function.BiConsumer;
  import java.util.function.Function;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  import java.math.BigInteger;
  
  import com.adacore.libfoolang.Libfoolang.*;
  
  public final class BindingsTests {
  
      // ----- Util methods -----
  
      /**
       * Display the section header with the given name
       *
       * @param name The name of the section
       */
      private static void header(String name) {
          System.out.println("--- " + name + " ---");
      }
  
      /**
       * Display the footer of a section
       *
       * @param name The name of the section
       */
      private static void footer(String name) {
          System.out.println("----" + "-".repeat(name.length()) + "----\n");
      }
  
      /**
       * Display the information about the tokens in the file
       *
       * @param file The file to get tokens from
       * @param withTrivia If the info should include trivia
       */
      private static void tokensInfo(String file, boolean withTrivia) {
          try(
              AnalysisContext context = AnalysisContext.create(
                  null,
                  null,
                  null,
                  null,
                  withTrivia,
                  8
              )
          ) {
              AnalysisUnit unit = context.getUnitFromFile(file);
              Token first = unit.getFirstToken();
              Token last = unit.getLastToken();
              FooNode root = unit.getRoot();
              Token rootStart = root.tokenStart();
              Token rootEnd = root.tokenEnd();
              System.out.println(
                  "  unit_text =\n    " + unit.getText() +
                  "  first_token = " + first.toString() +
                  "\n  first_text = " + first.getText() +
                  "\n  last_token = " + last.toString() +
                  "\n  last_text = " + last.getText() +
                  "\n  root_node = " + root.toString() +
                  "\n  root_start = " + rootStart.toString() +
                  "\n  root_end = " + rootEnd.toString() +
                  "\n  token_count = " + unit.getTokenCount() +
                  "\n  trivia_count = " + unit.getTriviaCount()
              );
          }
      }
  
      // ----- Test methods -----
  
      /**
       * Test the diagnostic manipulation
       */
      private static void testDiagnostics() {
          // Display header
          header("Diagnostics");
  
          // Prepare the working variables
          AnalysisUnit unit;
          Diagnostic[] diags;
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              // Create an unit from an unknown file
              unit = context.getUnitFromFile("unknown");
              diags = unit.getDiagnostics();
              for(Diagnostic diag : diags) {
                  System.out.println(diag.toString());
              }
  
              // Create a unit with a synthax error
              unit = context.getUnitFromBuffer("var identifier", "foo.txt");
              diags = unit.getDiagnostics();
              for(Diagnostic diag : diags) {
                  System.out.println(diag.toString());
              }
          }
  
          // Display footer
          footer("Diagnostics");
      }
  
      /**
       * Test the unit file name getting
       */
      private static void testUnitFileName() {
          // Display the header
          header("Unit Filename");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              // Get the unit for the foo.txt file
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              AnalysisUnit other = context.getUnitFromFile("foo.txt");
              AnalysisUnit notSame =
                  context.getUnitFromBuffer("var identifier", "bar.txt");
              System.out.println(
                  unit.toString() + " != " + other + " = " + (unit != other)
              );
              System.out.println(
                  unit.toString() + ".equals(" + other + ") = " +
                  unit.equals(other)
              );
              System.out.println(
                  unit.toString() + ".equals(" + notSame +
                  ") = " + unit.equals(notSame)
              );
              System.out.println(
                  "Unit for the file " +
                  new File(unit.getFileName()).getName()
              );
          }
  
          // Display the footer
          footer("Unit Filename");
      }
  
      /**
       * Test getting a unit from the unit provider.
       */
      private static void testUnitProvider() {
         header("Unit provider");
  
         AnalysisContext ctx = AnalysisContext.create();
         AnalysisUnit unit = ctx.getUnitFromProvider(
             Text.create("pkg"),
             AnalysisUnitKind.UNIT_BODY
         );
         System.out.println(
             "pkg/body resolved to: "
             + new File(unit.getFileName()).getName()
         );
  
         footer("Unit provider");
      }
  
      /**
       * Test the token manipulation
       */
      private static void testTokens() {
          // Display the header
          header("Tokens");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              // Test the trivia filtering
              System.out.println("--- Token info without trivia");
              tokensInfo("foo.txt", false);
              System.out.println("--- Token info with trivia");
              tokensInfo("foo.txt", true);
              System.out.println("");
  
              // Parse the foo.txt file to inspect its tokens
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              Token first = unit.getFirstToken();
              Token none = first.previous();
              System.out.println("The NO_TOKEN = " + none.toString());
              System.out.println("Text range with a NO_TOKEN = " +
                  Token.textRange(first, none));
  
              // Test the token equivalence and equality
              AnalysisUnit eqUnit = context.getUnitFromBuffer(
                  "null identifier example identifier example",
                  "foo.txt"
              );
              Token current = eqUnit.getFirstToken();
              while(!(current.isNone())) {
                  Token other = current.next();
                  if(!current.isTrivia()) {
                      while(!(other.isNone())) {
                          if(current.isEquivalent(other))
                              System.out.println(
                                  "Equivalent tokens : " +
                                  current.toString() + " and " +
                                  other.toString()
                              );
                          other = other.next();
                      }
                  }
                  current = current.next();
              }
  
              Token left = eqUnit.getFirstToken();
              Token right = eqUnit.getFirstToken();
              while(!(left.isNone() || right.isNone())) {
                  System.out.println(
                      left.toString() + " != " + right + " = " + (left != right)
                  );
                  System.out.println(
                      left.toString() + ".equals(" + right + ") = " +
                      left.equals(right)
                  );
                  left = left.next();
                  right = right.next();
              }
          }
  
          // Display the footer
          footer("Tokens");
      }
  
      /**
       * Test the node manipulation
       */
      private static void testNodes() {
          // Display the header
          header("Nodes");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              FooNode root = unit.getRoot();
  
              System.out.println("Unit = " + unit.toString());
              System.out.println("Unit root = " + root.toString());
              System.out.println("Unit root kind = " + root.getClassName());
              System.out.println(
                  "Unit root children count = " + root.getChildrenCount()
              );
              System.out.println(
                  "Unit root children = " + Arrays.toString(root.children())
              );
              System.out.println(
                  "Unit root tree dump = " + root.dumpTree()
              );
              System.out.println(
                  "Unit root is a list node = " + root.isListNode()
              );
              System.out.println(
                  "Unit root is a token node = " + root.isTokenNode()
              );
              System.out.println("Unit root image = " + root.getImage());
              System.out.println("Unit root text = " + root.getText());
              Sequence sequence = (Sequence) root;
              System.out.println("Iterating on root sequence:");
              for (FooNode node : sequence) {
                  System.out.println("  - " + node.toString());
              }
              FooNode[] items = sequence.pAllItems();
              System.out.println(
                  "Root \"p_all_items\" = " + Arrays.toString(items)
              );
              Example[] examples = sequence.pExampleItems();
              System.out.println(
                  "Root \"p_example_items\" = " + Arrays.toString(examples)
              );
              Var var = (Var) root.getChild(2);
              System.out.println("Var (3rd child) = " + var.toString());
              System.out.println("Var image = " + var.getImage());
              System.out.println(
                  "Var fields = " + Arrays.toString(var.getFieldNames())
              );
              Sequence arg = var.fArg();
              FooNode[] argItems = arg.pAllItems();
              System.out.println("Var arg = " + arg.toString());
              System.out.println("Var content = " + Arrays.toString(argItems));
              Var var2 = (Var) root.getChild(2);
              System.out.println("Node equality = " + var.equals(var2));
              System.out.println(
                  "Node hash equality = " + (var.hashCode() == var2.hashCode())
              );
              System.out.println(
                  "Node 'p_all_items' member reference equality = " +
                  (root.getFieldDescription("p_all_items").memberRef ==
                   MemberReference.FOO_SEQUENCE_P_ALL_ITEMS)
              );
  
              // Test node equality and hash codes
              FooNode e1 = root.pWithMd(true, false);
              FooNode e2 = root.pWithMd(true, true);
              FooNode e3 = root.pWithMd(false, true);
              System.out.println("e1.equals(e2) = " + e1.equals(e2));
              System.out.println("e1.equals(e3) = " + e1.equals(e3));
              System.out.println(
                  "hash(e1) == hash(e2) = " + (e1.hashCode() == e2.hashCode())
              );
              System.out.println(
                  "hash(e1) == hash(e3) = " + (e1.hashCode() == e3.hashCode())
              );
  
              // Test empty struct nullity
              EmptyStruct emptyStruct = root.pNewEmptyStruct();
              System.out.println(
                  "empty struct is_struct_null() = " +
                  root.pIsStructNull(emptyStruct)
              );
              System.out.println(
                  "null empty struct is_struct_null() = " +
                  root.pIsStructNull(EmptyStruct.NONE)
              );
          }
  
          // Display the footer
          footer("Nodes");
      }
  
      /**
       * Test the parent navigation
       */
      private static void testParent() {
          // Display the header
          header("Parent(s)");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              FooNode root = unit.getRoot();
  
              // Visit all the nodes and display their parents
              List<FooNode> visitList = new LinkedList<>();
              visitList.add(root);
              while(!visitList.isEmpty()) {
                  FooNode current = visitList.remove(0);
                  FooNode parent = current.parent();
                  System.out.println(
                      "Node " + current.toString() + " | Parents :"
                  );
                  while(!parent.isNone()) {
                      System.out.println("  " + parent.toString());
                      parent = parent.parent();
                  }
                  for(FooNode child : current.children()) {
                      if(!child.isNone()) visitList.add(child);
                  }
                  System.out.println("");
              }
              System.out.println(
                  "proposition (for all node n. " +
                  "for fields fields f of n. parent f = n) is true"
              );
          }
  
          // Display the footer
          footer("Parent(s)");
      }
  
      /**
       * Test the sibling navigation
       */
      private static void testSiblings() {
          // Display the header
          header("Siblings");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              Sequence root = (Sequence) unit.getRoot();
              FooNode[] items = root.pAllItems();
  
              // Visit all the items and display their siblings
              for(FooNode item : items) {
                  System.out.println(
                      item.toString() +
                      " | previous = " +
                      item.previousSibling() +
                      " | next = " +
                      item.nextSibling()
                  );
              }
          }
  
          // Display the footer
          footer("Siblings");
      }
  
      private static void testArrays() {
          // Display the header
          header("Arrays");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromBuffer(
                  "example null null example var (example null example)",
                  "foo.txt"
              );
              Sequence root = (Sequence) unit.getRoot();
  
              // Visit the sequence
              FooNode[] items = root.pAllItems();
              System.out.println("Sequence size = " + items.length);
              System.out.println("Sequence content :");
              for(int i = 0 ; i < items.length ; i++) {
                  FooNode item = items[i];
                  System.out.println("  " + item.toString());
              }
              FooNode[] eqItems = root.pAllItems();
              System.out.println("Array equality = " + items.equals(eqItems));
  
              // Test an array of primitive type (integer)
              int[] intArray = root.pGetIntArray();
              System.out.println("Integer array size = " + intArray.length);
              System.out.println(
                  "Integer array size (from property) = " +
                  root.pArrayLen(intArray)
              );
              System.out.println("Integer array content :");
              for (int i = 0; i < intArray.length; i++) {
                  System.out.println("  " + intArray[i]);
              }
  
              // Test a property error raised by an array typed property
              try {
                  FooNode[] invalid = root.pArrayPropError();
                  System.out.println("No exception raised by 'array_prop_error'");
              } catch (LangkitException exc) {
                  System.out.println(
                      "Expected exception: " + exc.kind + " - " +
                      exc.getMessage() + (
                          exc.nativeStackTrace() != "" ?
                              " (Stack trace available)" :
                              " (NO STACK TRACE)"
                      )
                  );
              }
  
          }
  
          // Display the footer
          footer("Arrays");
      }
  
      private static void testIterators() {
          header("Iterators");
  
          try (
              AnalysisContext context = AnalysisContext.create();
          ) {
              AnalysisUnit unit = context.getUnitFromBuffer(
                  "my_ident",
                  "foo.txt"
              );
              Sequence root = (Sequence) unit.getRoot();
  
              // Get and display an integer iterator
              System.out.println("--- Integer iterator");
              try (
                  IntegerIterator intIterator = root.pIterInt()
              ) {
                  while (intIterator.hasNext()) {
                      int i = intIterator.next();
                      System.out.println("  " + i);
                  }
              }
  
              // Get and display an entity iterator
              System.out.println("--- Entity iterator");
              try (
                  FooNodeIterator entityIterator = root.pIterEntity()
              ) {
                  while (entityIterator.hasNext()) {
                      FooNode i = entityIterator.next();
                      System.out.println("  " + i);
                  }
              }
  
              // Get and display a struct iterator (ensure handing of null values)
              System.out.println("--- Struct iterator");
              try (
                  SomeStructIterator structIterator = root.pIterStruct()
              ) {
                  while (structIterator.hasNext()) {
                      SomeStruct i = structIterator.next();
                      System.out.println(
                          "  SomeStruct(examples=" +
                          Arrays.toString(i.examples) +
                          ")"
                      );
                  }
              }
  
              // Check passing an iterator as property parameter
              System.out.println("--- Iterator as property parameter");
              try (
                  IntegerIterator iterator = root.pIterInt();
                  IntegerIterator same = root.pIterIntId(iterator)
              ) {
                  System.out.println(
                      "Identity iterator is the same = " + iterator.equals(same)
                  );
              }
          }
  
          footer("Iterators");
      }
  
      private static void testSymbols() {
          // Display the header
          header("Symbols");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromBuffer(
                  "my_ident",
                  "foo.txt"
              );
  
              Sequence root = (Sequence) unit.getRoot();
              Ident ident = (Ident) root.getChild(0);
  
              String[] sources = {
                  "my_ident",
                  "MY_IDENT",
                  "no_such_symbol",
                  "invalid_symbol0"
              };
              for(String source : sources) {
                  try {
                      Symbol symbol = ident.pSym(Symbol.create(source));
                      System.out.println(
                          "source = " +
                          source +
                          " | symbol = " +
                          symbol.text
                      );
                  } catch(SymbolException e) {
                      System.out.println(
                          "source = " +
                          source +
                          " | exception = " +
                          e.getMessage()
                      );
                  }
              }
          }
  
          // Display the footer
          footer("Symbols");
      }
  
      /**
       * Test the langkit characters
       */
      private static void testCharacter() {
          // Display the header
          header("Character");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromBuffer(
                  "my_ident",
                  "foo.txt"
              );
              FooNode root = unit.getRoot();
  
              // Test the character manipulation
              Reflection.Field getAField = root.getFieldDescription("p_get_a");
              Reflection.Field getEacuteField =
                  root.getFieldDescription("p_get_eacute");
              Char c1 = root.pGetA(
                  (Char) (
                      (Reflection.Param) getAField.params.get(0)
                  ).defaultValue.get()
              );
              Char c2 = root.pGetEacute(
                  (Char) (
                      (Reflection.Param) getEacuteField.params.get(0)
                  ).defaultValue.get()
              );
              Char cIdent = root.pIdentity(Char.create('\u00e9'));
              System.out.println("The 'a' char = " + c1.toString());
              System.out.println("The eacute char = " + String.valueOf(c2.value));
              System.out.println(
                  "Identity of eacute = " + String.valueOf(cIdent.value)
              );
          }
  
          // Display the footer
          footer("Character");
      }
  
      /**
       * Test the langkit strings
       */
      private static void testString() {
          // Display the header
          header("String");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromBuffer(
                  "my_ident",
                  "foo.txt"
              );
              FooNode root = unit.getRoot();
  
              // Try creating and passing strings
              String source = "Hello!";
              String emptySource = "";
              String identity = root.pGetStr(source);
              String emptyIdentity = root.pGetStr(emptySource);
              System.out.println(
                  "Source = \"" +
                  source +
                  "\" | Identity = \"" +
                  identity +
                  "\""
              );
              System.out.println(
                  "Empty source = \"" +
                  emptySource +
                  "\" | Empty identity = \"" +
                  emptyIdentity +
                  "\""
              );
          }
  
          // Display the footer
          footer("String");
      }
  
      /**
       * Test the langkit big int
       */
      private static void testBigInt() {
          // Display the header
          header("Big Integer");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              FooNode root = unit.getRoot();
  
              // Test the big integer manipulation
              BigInteger ft = new BigInteger("42");
              BigInteger big = new BigInteger(
                  "100000000000000000000000000000000000000000000000" +
                  "00000000000000000000000000000000000000000000000000000"
              );
              BigInteger ftDouble = root.pIntDouble(ft);
              BigInteger bigDouble = root.pIntDouble(big);
              System.out.println(
                  "Double of " +
                  ft.toString() +
                  " = " +
                  ftDouble.toString()
              );
              System.out.println(
                  "Double of 10**100 = " +
                  bigDouble.toString()
              );
          }
  
          // Display the footer
          footer("Big Integer");
      }
  
      private static void testStruct() {
          // Display the header
          header("Struct");
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              FooNode root = unit.getRoot();
  
              // Test a struct with an entity
              FooNodeStruct falseNodeStruct = root.pMe(false);
              FooNodeStruct trueNodeStruct = root.pMe(true);
              FooNode falseNode = root.pGetNode(falseNodeStruct);
              FooNode trueNode = root.pGetNode(trueNodeStruct);
              System.out.println("me false = " + falseNode);
              System.out.println("me true = " + trueNode);
              System.out.println(
                  "falseNode.equals(falseNodeStruct.node) = " +
                  falseNode.equals(falseNodeStruct.node)
              );
  
              // Test a struct with a bare node and verify that it has been
              // wrapped.
              BareNodeStruct bareNodeStruct = root.pMyNode();
              FooNode bareNode = bareNodeStruct.bareNode;
              System.out.println("root.pMyNode().bareNode = " + bareNode);
  
              // Test passing this structure as a property parameter
              Char aChar = bareNodeStruct.aChar;
              Char otherChar = root.pGetChar(bareNodeStruct);
              System.out.println(
                  "aChar.equals(otherChar) = " + aChar.equals(otherChar)
              );
          }
  
          // Display the footer
          footer("Struct");
      }
  
      private static void testEventHandlers() {
          header("Event handlers");
  
          // Local function to test event handlers
          BiConsumer<
              EventHandler.UnitRequestedCallback,
              EventHandler.UnitParsedCallback
          > testHandler = (unitRequestedCallback, unitParsedCallback) -> {
              // Create the context with the event handler and create units
              try(
                  EventHandler eventHandler = EventHandler.create(
                      unitRequestedCallback,
                      unitParsedCallback
                  );
                  AnalysisContext context = AnalysisContext.create(
                      null,
                      null,
                      null,
                      eventHandler,
                      true,
                      8
                  );
              ) {
                  // Parse the unit twice to test the "reparsed" callback param
                  AnalysisUnit unit = context.getUnitFromBuffer(
                      "example",
                      "example"
                  );
                  unit = context.getUnitFromBuffer("example example", "example");
  
                  // Call the property to trigger "unit requested" callback
                  FooNode root = unit.getRoot();
                  root.pTriggerUnitRequested(
                      Symbol.create("foo_a"),
                      false,
                      false
                  );
                  root.pTriggerUnitRequested(
                      Symbol.create("foo_b"),
                      true,
                      false
                  );
                  root.pTriggerUnitRequested(
                      Symbol.create("foo_c"),
                      true,
                      true
                  );
                  root.pTriggerUnitRequested(
                      Symbol.create("foo_d"),
                      false,
                      true
                  );
              }
          };
  
          // Create the callback functions
          EventHandler.UnitRequestedCallback unitRequestedCallback = (
              AnalysisContext context,
              String name,
              AnalysisUnit from,
              boolean found,
              boolean isNotFoundError
          ) -> {
              System.out.println("--- Unit requested callback");
              System.out.println("name: " + name);
              System.out.println("from: " + from);
              System.out.println("found: " + found);
              System.out.println("is_not_found_error: " + isNotFoundError);
              System.out.println();
          };
          EventHandler.UnitParsedCallback unitParsedCallback = (
              AnalysisContext context,
              AnalysisUnit unit,
              boolean reparsed
          ) -> {
              System.out.println("--- Unit parsed callback");
              System.out.println("unit: " + unit);
              System.out.println("reparsed: " + reparsed);
              System.out.println();
          };
  
          System.out.println("=== Non null callbacks ===");
          testHandler.accept(
              unitRequestedCallback,
              unitParsedCallback
          );
          System.out.println("=== Null callbacks ===");
          testHandler.accept(null, null);
  
          System.out.println("=== Unclosed event handler ===");
          EventHandler dontClose = EventHandler.create(
              unitRequestedCallback,
              unitParsedCallback
          );
  
          footer("Event handlers");
      }
  
      private static class FooVisitor extends DefaultVisitor<Integer> {
  
          protected Function<FooNode, Integer> createDefaultBehavior() {
              return (node) -> {
                  System.out.println("Visiting " + node.getImage());
                  Integer count = 1;
                  for (var c : node.children()) {
                      if (!c.isNone()) count += c.accept(this);
                  }
                  return count;
              };
          }
      }
  
      private static void testDefaultVisitor() {
          header("Default visitor");
          FooVisitor visitor = new FooVisitor();
  
          try(
              AnalysisContext context = AnalysisContext.create()
          ) {
              AnalysisUnit unit = context.getUnitFromFile("foo.txt");
              Integer count = unit.getRoot().accept(visitor);
              System.out.println("Visited " + String.valueOf(count) + " nodes");
          }
  
          footer("Default visitor");
      }
      /**
       * Run the Java tests one by one
       *
       * @param args The arguments for the tests running
       */
      public static void main(String[] args) {
          // Run the tests
          System.out.println("===== Start the Java tests =====\n");
          testDiagnostics();
          testUnitFileName();
          testUnitProvider();
          testTokens();
          testNodes();
          testParent();
          testSiblings();
          testArrays();
          testIterators();
          testSymbols();
          testCharacter();
          testString();
          testBigInt();
          testStruct();
          testEventHandlers();
          testDefaultVisitor();
          System.out.println("===== End of the Java tests =====");
      }
  
  }
  