/**
 * 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.yaml.snakeyaml.recursive;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import junit.framework.TestCase;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Util;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.YamlCreator;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.inspector.TrustedTagInspector;
import org.yaml.snakeyaml.representer.Representer;

public class HumanTest extends TestCase {

  public void testNoChildren() {
    Human father = new Human();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    Human mother = new Human();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    Yaml yaml = YamlCreator.allowClassPrefix("org.yaml.snakeyaml");
    String output = yaml.dump(father);
    String etalon = Util.getLocalResource("recursive/no-children-1.yaml");
    assertEquals(etalon, output);
    //
    Human father2 = yaml.load(output);
    assertNotNull(father2);
    assertEquals("Father", father2.getName());
    assertEquals("Mother", father2.getPartner().getName());
    assertEquals("Father", father2.getBankAccountOwner().getName());
    assertSame(father2, father2.getBankAccountOwner());
  }

  public void testNoChildrenPretty() {
    Human father = new Human();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    Human mother = new Human();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    DumperOptions options = new DumperOptions();
    options.setPrettyFlow(true);
    options.setDefaultFlowStyle(FlowStyle.FLOW);
    Yaml yaml = new Yaml(options);
    String output = yaml.dump(father);
    String etalon = Util.getLocalResource("recursive/no-children-1-pretty.yaml");
    assertEquals(etalon, output);
    //
    Human father2 = YamlCreator.allowClassPrefix("org.yaml.snakeyaml").load(output);
    assertNotNull(father2);
    assertEquals("Father", father2.getName());
    assertEquals("Mother", father2.getPartner().getName());
    assertEquals("Father", father2.getBankAccountOwner().getName());
    assertSame(father2, father2.getBankAccountOwner());
  }

  public void testChildren() {
    Human father = new Human();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    //
    Human mother = new Human();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    //
    Human son = new Human();
    son.setName("Son");
    son.setBirthday(new Date(310000000000L));
    son.setBirthPlace("Munich");
    son.setBankAccountOwner(father);
    son.setFather(father);
    son.setMother(mother);
    //
    Human daughter = new Human();
    daughter.setName("Daughter");
    daughter.setBirthday(new Date(420000000000L));
    daughter.setBirthPlace("New York");
    daughter.setBankAccountOwner(father);
    daughter.setFather(father);
    daughter.setMother(mother);
    //
    Set<Human> children = new LinkedHashSet<Human>(2);
    children.add(son);
    children.add(daughter);
    father.setChildren(children);
    mother.setChildren(children);
    //
    Yaml beanDumper = new Yaml();
    String output = beanDumper.dumpAsMap(son);
    // System.out.println(output);
    String etalon = Util.getLocalResource("recursive/with-children.yaml");
    assertEquals(etalon, output);
    TypeDescription humanDescription = new TypeDescription(Human.class);
    humanDescription.putMapPropertyType("children", Human.class, Object.class);

    LoaderOptions loaderOptions = new LoaderOptions();
    loaderOptions.setAllowRecursiveKeys(true);
    Yaml beanLoader = new Yaml(new Constructor(humanDescription, loaderOptions));
    //
    Human son2 = beanLoader.loadAs(output, Human.class);
    assertNotNull(son2);
    assertEquals("Son", son.getName());

    Human father2 = son2.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", son2.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), son2.getMother());
    assertSame(father2, son2.getMother().getPartner());

    Set<Human> children2 = father2.getChildren();
    assertEquals(2, children2.size());
    assertSame(father2.getPartner().getChildren(), children2);

    for (Object child : children2) {
      // check if type descriptor was correct
      assertSame(Human.class, child.getClass());
    }

    // check if hashCode is correct
    validateSet(children2);
  }

  public void testChildrenPretty() {
    Human father = new Human();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    //
    Human mother = new Human();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    //
    Human son = new Human();
    son.setName("Son");
    son.setBirthday(new Date(310000000000L));
    son.setBirthPlace("Munich");
    son.setBankAccountOwner(father);
    son.setFather(father);
    son.setMother(mother);
    //
    Human daughter = new Human();
    daughter.setName("Daughter");
    daughter.setBirthday(new Date(420000000000L));
    daughter.setBirthPlace("New York");
    daughter.setBankAccountOwner(father);
    daughter.setFather(father);
    daughter.setMother(mother);
    //
    Set<Human> children = new LinkedHashSet<Human>(2);
    children.add(son);
    children.add(daughter);
    father.setChildren(children);
    mother.setChildren(children);
    //
    DumperOptions options = new DumperOptions();
    options.setDefaultFlowStyle(FlowStyle.FLOW);
    options.setPrettyFlow(true);
    Yaml beanDumper = new Yaml(options);
    String output = beanDumper.dump(son);
    // System.out.println(output);
    String etalon = Util.getLocalResource("recursive/with-children-pretty.yaml");
    assertEquals(etalon, output);
    TypeDescription humanDescription = new TypeDescription(Human.class);
    humanDescription.putMapPropertyType("children", Human.class, Object.class);

    LoaderOptions loaderOptions = new LoaderOptions();
    loaderOptions.setAllowRecursiveKeys(true);
    loaderOptions.setTagInspector(new TrustedTagInspector());
    Yaml beanLoader = new Yaml(new Constructor(humanDescription, loaderOptions));
    //
    Human son2 = beanLoader.loadAs(output, Human.class);
    assertNotNull(son2);
    assertEquals("Son", son.getName());

    Human father2 = son2.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", son2.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), son2.getMother());
    assertSame(father2, son2.getMother().getPartner());

    Set<Human> children2 = father2.getChildren();
    assertEquals(2, children2.size());
    assertSame(father2.getPartner().getChildren(), children2);

    for (Object child : children2) {
      // check if type descriptor was correct
      assertSame(Human.class, child.getClass());
    }

    // check if hashCode is correct
    validateSet(children2);
  }

  public void testChildren2() {
    Human2 father = new Human2();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    //
    Human2 mother = new Human2();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    //
    Human2 son = new Human2();
    son.setName("Son");
    son.setBirthday(new Date(310000000000L));
    son.setBirthPlace("Munich");
    son.setBankAccountOwner(father);
    son.setFather(father);
    son.setMother(mother);
    //
    Human2 daughter = new Human2();
    daughter.setName("Daughter");
    daughter.setBirthday(new Date(420000000000L));
    daughter.setBirthPlace("New York");
    daughter.setBankAccountOwner(father);
    daughter.setFather(father);
    daughter.setMother(mother);
    //
    HashMap<Human2, String> children = new LinkedHashMap<Human2, String>(2);
    children.put(son, "son");
    children.put(daughter, "daughter");
    father.setChildren(children);
    mother.setChildren(children);
    //

    LoaderOptions options = new LoaderOptions();
    options.setAllowRecursiveKeys(true);
    options.setTagInspector(new TrustedTagInspector());
    Constructor constructor = new Constructor(Human2.class, options);
    TypeDescription humanDescription = new TypeDescription(Human2.class);
    humanDescription.putMapPropertyType("children", Human2.class, String.class);
    constructor.addTypeDescription(humanDescription);

    Yaml yaml =
        new Yaml(constructor, new Representer(new DumperOptions()), new DumperOptions(), options);
    String output = yaml.dump(son);
    // System.out.println(output);
    String etalon = Util.getLocalResource("recursive/with-children-2.yaml");
    assertEquals(etalon, output);
    //
    Human2 son2 = yaml.load(output);
    assertNotNull(son2);
    assertEquals("Son", son.getName());

    Human2 father2 = son2.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", son2.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), son2.getMother());
    assertSame(father2, son2.getMother().getPartner());

    Map<Human2, String> children2 = father2.getChildren();
    assertEquals(2, children2.size());
    assertSame(father2.getPartner().getChildren(), children2);

    validateMapKeys(children2);
  }

  public void testChildren3() {
    Human3 father = new Human3();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    //
    Human3 mother = new Human3();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    //
    Human3 son = new Human3();
    son.setName("Son");
    son.setBirthday(new Date(310000000000L));
    son.setBirthPlace("Munich");
    son.setBankAccountOwner(father);
    son.setFather(father);
    son.setMother(mother);
    //
    Human3 daughter = new Human3();
    daughter.setName("Daughter");
    daughter.setBirthday(new Date(420000000000L));
    daughter.setBirthPlace("New York");
    daughter.setBankAccountOwner(father);
    daughter.setFather(father);
    daughter.setMother(mother);
    //
    ArrayList<Human3> children = new ArrayList<Human3>();
    children.add(son);
    children.add(daughter);
    father.setChildren(children);
    mother.setChildren(children);
    //

    Constructor constructor =
        new Constructor(Human3.class, YamlCreator.trustPrefixLoaderOptions("org.yaml.snakeyaml"));
    TypeDescription Human3Description = new TypeDescription(Human3.class);
    Human3Description.putListPropertyType("children", Human3.class);
    constructor.addTypeDescription(Human3Description);

    Yaml yaml = new Yaml(constructor);
    String output = yaml.dump(son);
    // System.out.println(output);
    String etalon = Util.getLocalResource("recursive/with-children-3.yaml");
    assertEquals(etalon, output);
    //
    Human3 son2 = yaml.load(output);
    assertNotNull(son2);
    assertEquals("Son", son.getName());

    Human3 father2 = son2.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", son2.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), son2.getMother());
    assertSame(father2, son2.getMother().getPartner());

    List<Human3> children2 = father2.getChildren();
    assertEquals(2, children2.size());
    assertSame(father2.getPartner().getChildren(), children2);

    for (Object child : children2) {
      // check if type descriptor was correct
      assertSame(Human3.class, child.getClass());
    }
  }

  /*
   * Loads same structure as created in testChildren. But root object is set of children
   */
  @SuppressWarnings("unchecked")
  public void testChildrenSetAsRoot() {
    String etalon = Util.getLocalResource("recursive/with-children-as-set.yaml");

    Constructor constructor = new Constructor(YamlCreator.trustedLoaderOptions());
    TypeDescription humanDescription = new TypeDescription(Human.class);
    humanDescription.putMapPropertyType("children", Human.class, Object.class);
    constructor.addTypeDescription(humanDescription);

    Yaml yaml = new Yaml(constructor);
    Set<Human> children2 = yaml.load(etalon);
    assertNotNull(children2);
    assertEquals(2, children2.size());

    Human firstChild = children2.iterator().next();

    Human father2 = firstChild.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", firstChild.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), firstChild.getMother());
    assertSame(father2, firstChild.getMother().getPartner());

    assertSame(father2.getPartner().getChildren(), children2);

    for (Object child : children2) {
      // check if type descriptor was correct
      assertSame(Human.class, child.getClass());
    }

    validateSet(children2);
  }

  /*
   * Loads same structure as created in testChildren. But root object is map of children
   */
  @SuppressWarnings("unchecked")
  public void testChildrenMapAsRoot() {
    String etalon = Util.getLocalResource("recursive/with-children-as-map.yaml");

    Constructor constructor = new Constructor(YamlCreator.trustedLoaderOptions());
    TypeDescription Human2Description = new TypeDescription(Human2.class);
    Human2Description.putMapPropertyType("children", Human2.class, String.class);
    constructor.addTypeDescription(Human2Description);

    Yaml yaml = new Yaml(constructor);
    Map<Human2, String> children2 = yaml.load(etalon);
    assertNotNull(children2);
    assertEquals(2, children2.size());

    Entry<Human2, String> firstEntry = children2.entrySet().iterator().next();
    Human2 firstChild = firstEntry.getKey();

    Human2 father2 = firstChild.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", firstChild.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), firstChild.getMother());
    assertSame(father2, firstChild.getMother().getPartner());

    assertSame(father2.getPartner().getChildren(), children2);

    validateMapKeys(children2);
  }

  /*
   * Loads same structure as created in testChildren. But root object is list of children
   */
  @SuppressWarnings("unchecked")
  public void testChildrenListRoot() {
    Human3 father = new Human3();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    //
    Human3 mother = new Human3();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    //
    Human3 son = new Human3();
    son.setName("Son");
    son.setBirthday(new Date(310000000000L));
    son.setBirthPlace("Munich");
    son.setBankAccountOwner(father);
    son.setFather(father);
    son.setMother(mother);
    //
    Human3 daughter = new Human3();
    daughter.setName("Daughter");
    daughter.setBirthday(new Date(420000000000L));
    daughter.setBirthPlace("New York");
    daughter.setBankAccountOwner(father);
    daughter.setFather(father);
    daughter.setMother(mother);
    //
    ArrayList<Human3> children = new ArrayList<Human3>();
    children.add(son);
    children.add(daughter);
    father.setChildren(children);
    mother.setChildren(children);
    //

    Constructor constructor =
        new Constructor(YamlCreator.trustPrefixLoaderOptions("org.yaml.snakeyaml"));
    TypeDescription Human3Description = new TypeDescription(Human3.class);
    Human3Description.putListPropertyType("children", Human3.class);
    constructor.addTypeDescription(Human3Description);

    Yaml yaml = new Yaml(constructor);
    String output = yaml.dump(father.getChildren());
    // System.out.println(output);
    String etalon = Util.getLocalResource("recursive/with-children-as-list.yaml");
    assertEquals(etalon, output);
    //
    List<Human3> children2 = yaml.load(output);
    assertNotNull(children2);
    Human3 son2 = children2.iterator().next();
    assertEquals(2, children2.size());

    Human3 father2 = son2.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", son2.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), son2.getMother());
    assertSame(father2, son2.getMother().getPartner());

    assertSame(father2.getPartner().getChildren(), children2);

    for (Object child : children2) {
      // check if type descriptor was correct
      assertSame(Human3.class, child.getClass());
    }
  }

  public void testBeanRing() {
    Human man1 = new Human();
    man1.setName("Man 1");
    Human man2 = new Human();
    man2.setName("Man 2");
    Human man3 = new Human();
    man3.setName("Man 3");
    man1.setBankAccountOwner(man2);
    man2.setBankAccountOwner(man3);
    man3.setBankAccountOwner(man1);
    //
    Yaml yaml = YamlCreator.allowClassPrefix("org.yaml.snakeyaml");
    String output = yaml.dump(man1);
    // System.out.println(output);
    String etalon = Util.getLocalResource("recursive/beanring-3.yaml");
    assertEquals(etalon, output);
    //
    Human loadedMan1 = yaml.load(output);
    assertNotNull(loadedMan1);
    assertEquals("Man 1", loadedMan1.getName());
    Human loadedMan2 = loadedMan1.getBankAccountOwner();
    Human loadedMan3 = loadedMan2.getBankAccountOwner();
    assertSame(loadedMan1, loadedMan3.getBankAccountOwner());
  }

  public void qtestCollectionRing() {
    // Set<Object> set = new HashSet<Object>();
    // List<Object> list = new ArrayList<Object>();
    // Map<Object, Object> map = new HashMap<Object, Object>();
    // set.add(list);
    // list.add(map);
    // map.put("1", set);
    // //
    // try {
    // Yaml yaml = new Yaml();
    // String output = yaml.dump(set);
    // // String etalon = Util.getLocalResource("recursive/???.yaml");
    // // assertEquals(etalon, output);
    // //
    // // Set<Object> loadedSet = (Set<Object>) yaml.load(output);
    // } catch (StackOverflowError e) {
    // fail("Cannot dump recursive collections.");
    // }
  }

  /**
   * Checks if object was put into the set after full construction. So the hashCode was calculated
   * correctly (if it depends on internal object's state).
   *
   * @param set
   */
  private void validateSet(Set<?> set) {
    for (Object object : set) {
      assertTrue(set.contains(object));
    }
  }

  /**
   * Checks if object was put into the map as key after full construction. So the hashCode was
   * calculated correctly (if it depends on internal object's state).
   *
   * @param map
   */
  private void validateMapKeys(Map<?, ?> map) {
    for (Map.Entry<?, ?> entry : map.entrySet()) {
      assertTrue(map.containsKey(entry.getKey()));
    }
  }

  public void testChildrenWithoutRootTag() {
    Human father = new Human();
    father.setName("Father");
    father.setBirthday(new Date(1000000000));
    father.setBirthPlace("Leningrad");
    father.setBankAccountOwner(father);
    //
    Human mother = new Human();
    mother.setName("Mother");
    mother.setBirthday(new Date(100000000000L));
    mother.setBirthPlace("Saint-Petersburg");
    father.setPartner(mother);
    mother.setPartner(father);
    mother.setBankAccountOwner(father);
    //
    Human son = new Human();
    son.setName("Son");
    son.setBirthday(new Date(310000000000L));
    son.setBirthPlace("Munich");
    son.setBankAccountOwner(father);
    son.setFather(father);
    son.setMother(mother);
    //
    Human daughter = new Human();
    daughter.setName("Daughter");
    daughter.setBirthday(new Date(420000000000L));
    daughter.setBirthPlace("New York");
    daughter.setBankAccountOwner(father);
    daughter.setFather(father);
    daughter.setMother(mother);
    //
    Set<Human> children = new LinkedHashSet<Human>(2);
    children.add(son);
    children.add(daughter);
    father.setChildren(children);
    mother.setChildren(children);
    //
    Yaml beanDumper = new Yaml();
    String output = beanDumper.dumpAsMap(son);
    // System.out.println(output);
    String etalon = Util.getLocalResource("recursive/with-children-no-root-tag.yaml");
    assertEquals(etalon, output);
    TypeDescription humanDescription = new TypeDescription(Human.class);
    humanDescription.putMapPropertyType("children", Human.class, Object.class);
    LoaderOptions options = new LoaderOptions();
    options.setAllowRecursiveKeys(true);
    Yaml beanLoader = new Yaml(new Constructor(humanDescription, options));
    //
    Human son2 = beanLoader.loadAs(output, Human.class);
    assertNotNull(son2);
    assertEquals("Son", son.getName());

    Human father2 = son2.getFather();
    assertEquals("Father", father2.getName());
    assertEquals("Mother", son2.getMother().getName());
    assertSame(father2, father2.getBankAccountOwner());
    assertSame(father2.getPartner(), son2.getMother());
    assertSame(father2, son2.getMother().getPartner());

    Set<Human> children2 = father2.getChildren();
    assertEquals(2, children2.size());
    assertSame(father2.getPartner().getChildren(), children2);

    for (Object child : children2) {
      // check if type descriptor was correct
      assertSame(Human.class, child.getClass());
    }

    // check if hashCode is correct
    validateSet(children2);
  }
}
