/**
 * Copyright (c) 2008, SnakeYAML
 *
 * 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
 *
 * 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.pyyaml;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.composer.Composer;
import org.yaml.snakeyaml.constructor.AbstractConstruct;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.CollectionStartEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.ScalarEvent;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.parser.ParserImpl;
import org.yaml.snakeyaml.reader.StreamReader;
import org.yaml.snakeyaml.reader.UnicodeReader;
import org.yaml.snakeyaml.resolver.Resolver;

/**
 * imported from PyYAML
 */
public class PyStructureTest extends PyImportTest {

  private void compareEvents(List<Event> events1, List<Event> events2, boolean full) {
    assertEquals(events1.size(), events2.size());
    Iterator<Event> iter1 = events1.iterator();
    Iterator<Event> iter2 = events2.iterator();
    while (iter1.hasNext()) {
      Event event1 = iter1.next();
      Event event2 = iter2.next();
      assertEquals(event1.getClass(), event2.getClass());
      if (event1 instanceof AliasEvent && full) {
        assertEquals(((AliasEvent) event1).getAnchor(), ((AliasEvent) event2).getAnchor());
      }
      if (event1 instanceof CollectionStartEvent) {
        String tag1 = ((CollectionStartEvent) event1).getTag();
        String tag2 = ((CollectionStartEvent) event1).getTag();
        if (tag1 != null && !"!".equals(tag1) && tag2 != null && !"!".equals(tag1)) {
          assertEquals(tag1, tag2);
        }
      }
      if (event1 instanceof ScalarEvent) {
        ScalarEvent scalar1 = (ScalarEvent) event1;
        ScalarEvent scalar2 = (ScalarEvent) event2;
        if (scalar1.getImplicit().bothFalse() && scalar2.getImplicit().bothFalse()) {
          assertEquals(scalar1.getTag(), scalar2.getTag());
        }
        assertEquals(scalar1.getValue(), scalar2.getValue());
      }
    }
  }

  public void testParser() {
    File[] files = getStreamsByExtension(".data", true);
    assertTrue("No test files found.", files.length > 0);
    for (File file : files) {
      if (!file.getName().contains("scan-line-b")) {
        continue;
      }
      try {
        InputStream input = new FileInputStream(file);
        List<Event> events1 = parse(input);
        input.close();
        assertFalse(events1.isEmpty());
        int index = file.getAbsolutePath().lastIndexOf('.');
        String canonicalName = file.getAbsolutePath().substring(0, index) + ".canonical";
        File canonical = new File(canonicalName);
        List<Event> events2 = canonicalParse(new FileInputStream(canonical));
        assertFalse(events2.isEmpty());
        compareEvents(events1, events2, false);
      } catch (Exception e) {
        System.out.println("Failed File: " + file);
        // fail("Failed File: " + file + "; " + e.getMessage());
        throw new RuntimeException(e);
      }
    }
  }

  public void testParserOnCanonical() {
    File[] canonicalFiles = getStreamsByExtension(".canonical", false);
    assertTrue("No test files found.", canonicalFiles.length > 0);
    for (File file : canonicalFiles) {
      try {
        InputStream input = new FileInputStream(file);
        List<Event> events1 = parse(input);
        input.close();
        assertFalse(events1.isEmpty());
        List<Event> events2 = canonicalParse(new FileInputStream(file));
        assertFalse(events2.isEmpty());
        compareEvents(events1, events2, true);
      } catch (Exception e) {
        System.out.println("Failed File: " + file);
        // fail("Failed File: " + file + "; " + e.getMessage());
        throw new RuntimeException(e);
      }
    }
  }

  private void compareNodes(Node node1, Node node2) {
    assertEquals(node1.getClass(), node2.getClass());
    if (node1 instanceof ScalarNode) {
      ScalarNode scalar1 = (ScalarNode) node1;
      ScalarNode scalar2 = (ScalarNode) node2;
      assertEquals(scalar1.getTag(), scalar2.getTag());
      assertEquals(scalar1.getValue(), scalar2.getValue());
    } else {
      if (node1 instanceof SequenceNode) {
        SequenceNode seq1 = (SequenceNode) node1;
        SequenceNode seq2 = (SequenceNode) node2;
        assertEquals(seq1.getTag(), seq2.getTag());
        assertEquals(seq1.getValue().size(), seq2.getValue().size());
        Iterator<Node> iter2 = seq2.getValue().iterator();
        for (Node child1 : seq1.getValue()) {
          Node child2 = iter2.next();
          compareNodes(child1, child2);
        }
      } else {
        MappingNode seq1 = (MappingNode) node1;
        MappingNode seq2 = (MappingNode) node2;
        assertEquals(seq1.getTag(), seq2.getTag());
        assertEquals(seq1.getValue().size(), seq2.getValue().size());
        Iterator<NodeTuple> iter2 = seq2.getValue().iterator();
        for (NodeTuple child1 : seq1.getValue()) {
          NodeTuple child2 = iter2.next();
          compareNodes(child1.getKeyNode(), child2.getKeyNode());
          compareNodes(child1.getValueNode(), child2.getValueNode());
        }
      }
    }
  }

  public void testComposer() {
    File[] files = getStreamsByExtension(".data", true);
    assertTrue("No test files found.", files.length > 0);
    for (File file : files) {
      try {
        InputStream input = new FileInputStream(file);
        List<Node> events1 = compose_all(input);
        input.close();
        int index = file.getAbsolutePath().lastIndexOf('.');
        String canonicalName = file.getAbsolutePath().substring(0, index) + ".canonical";
        File canonical = new File(canonicalName);
        InputStream input2 = new FileInputStream(canonical);
        List<Node> events2 = canonical_compose_all(input2);
        input2.close();
        assertEquals(events1.size(), events2.size());
        Iterator<Node> iter1 = events1.iterator();
        Iterator<Node> iter2 = events2.iterator();
        while (iter1.hasNext()) {
          compareNodes(iter1.next(), iter2.next());
        }
      } catch (Exception e) {
        System.out.println("Failed File: " + file);
        // fail("Failed File: " + file + "; " + e.getMessage());
        throw new RuntimeException(e);
      }
    }
  }

  private List<Node> compose_all(InputStream file) {
    LoaderOptions options = new LoaderOptions();
    Composer composer =
        new Composer(new ParserImpl(new StreamReader(new UnicodeReader(file)), options),
            new Resolver(), options);
    List<Node> documents = new ArrayList<Node>();
    while (composer.checkNode()) {
      documents.add(composer.getNode());
    }
    return documents;
  }

  private List<Node> canonical_compose_all(InputStream file) {
    StreamReader reader = new StreamReader(new UnicodeReader(file));
    StringBuilder buffer = new StringBuilder();
    while (reader.peek() != '\0') {
      buffer.appendCodePoint(reader.peek());
      reader.forward();
    }
    CanonicalParser parser =
        new CanonicalParser(buffer.toString().replace(System.lineSeparator(), "\n"));
    Composer composer = new Composer(parser, new Resolver(), new LoaderOptions());
    List<Node> documents = new ArrayList<Node>();
    while (composer.checkNode()) {
      documents.add(composer.getNode());
    }
    return documents;
  }

  class CanonicalLoader extends Yaml {

    public CanonicalLoader() {
      super(new MyConstructor());
    }

    @Override
    public Iterable<Object> loadAll(Reader yaml) {
      StreamReader reader = new StreamReader(yaml);
      StringBuilder buffer = new StringBuilder();
      while (reader.peek() != '\0') {
        buffer.appendCodePoint(reader.peek());
        reader.forward();
      }
      CanonicalParser parser =
          new CanonicalParser(buffer.toString().replace(System.lineSeparator(), "\n"));
      Composer composer = new Composer(parser, resolver, new LoaderOptions());
      this.constructor.setComposer(composer);
      Iterator<Object> result = new Iterator<Object>() {
        public boolean hasNext() {
          return constructor.checkData();
        }

        public Object next() {
          return constructor.getData();
        }

        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
      return new YamlIterable(result);
    }

    private class YamlIterable implements Iterable<Object> {

      private final Iterator<Object> iterator;

      public YamlIterable(Iterator<Object> iterator) {
        this.iterator = iterator;
      }

      public Iterator<Object> iterator() {
        return iterator;
      }

    }

  }

  private class MyConstructor extends Constructor {

    public MyConstructor() {
      super(new LoaderOptions());
      this.yamlConstructors.put(null, new ConstructUndefined());
    }

    private class ConstructUndefined extends AbstractConstruct {

      public Object construct(Node node) {
        return constructScalar((ScalarNode) node);
      }
    }
  }

  public void testConstructor() {
    File[] files = getStreamsByExtension(".data", true);
    assertTrue("No test files found.", files.length > 0);
    Yaml myYaml = new Yaml(new MyConstructor());
    Yaml canonicalYaml = new CanonicalLoader();
    for (File file : files) {
      try {
        InputStream input = new FileInputStream(file);
        Iterable<Object> documents1 = myYaml.loadAll(input);
        int index = file.getAbsolutePath().lastIndexOf('.');
        String canonicalName = file.getAbsolutePath().substring(0, index) + ".canonical";
        File canonical = new File(canonicalName);
        InputStream input2 = new FileInputStream(canonical);
        Iterable<Object> documents2 = canonicalYaml.loadAll(input2);
        input2.close();
        Iterator<Object> iter2 = documents2.iterator();
        for (Object object1 : documents1) {
          Object object2 = iter2.next();
          if (object2 != null) {
            assertFalse(System.identityHashCode(object1) == System.identityHashCode(object2));
          }
          assertEquals("" + object1, object1, object2);
        }
        input.close();
      } catch (Exception e) {
        System.out.println("Failed File: " + file);
        // fail("Failed File: " + file + "; " + e.getMessage());
        throw new RuntimeException(e);
      }
    }
  }
}
