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

import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
import org.junit.Assert;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.YamlCreator;
import org.yaml.snakeyaml.constructor.ConstructorException;
import org.yaml.snakeyaml.error.YAMLException;

public class PrimitiveArrayTest extends TestCase {

  private final String pkg = "!!org.yaml.snakeyaml.array";

  private final byte[] bytes = new byte[] {1, 2, 3};
  private final short[] shorts = new short[] {300, 301, 302};
  private final int[] ints = new int[] {40000, 40001, 40002};
  private final long[] longs = new long[] {5000000000L, 5000000001L};
  private final float[] floats = new float[] {0.1f, 3.1415f};
  private final double[] doubles = new double[] {50.0001, 2150.0002};
  private final char[] chars = new char[] {'a', 'b', 'c', 'd', 'e'};
  private final boolean[] bools = new boolean[] {true, false};

  private Yaml createYaml() {
    return YamlCreator.allowClassPrefix("org.yaml.snakeyaml.array");
  }

  public void testValidConstructor() {
    String testInput = "- " + pkg + ".ByteArr [ " + Arrays.toString(bytes) + " ]\n" + "- " + pkg
        + ".ShortArr [ " + Arrays.toString(shorts) + " ]\n" + "- " + pkg + ".IntArr [ "
        + Arrays.toString(ints) + " ]\n" + "- " + pkg + ".LongArr [ " + Arrays.toString(longs)
        + " ]\n" + "- " + pkg + ".FloatArr [ " + Arrays.toString(floats) + " ]\n" + "- " + pkg
        + ".DoubleArr [ " + Arrays.toString(doubles) + " ]\n" + "- " + pkg + ".CharArr [ "
        + Arrays.toString(chars) + " ]\n" + "- " + pkg + ".BooleanArr [ " + Arrays.toString(bools)
        + " ]\n";

    Yaml yaml = createYaml();
    List<Object> wrappers = yaml.load(testInput);

    Assert.assertArrayEquals(bytes, ((ByteArr) wrappers.get(0)).getBytes());
    Assert.assertArrayEquals(shorts, ((ShortArr) wrappers.get(1)).getShorts());
    Assert.assertArrayEquals(ints, ((IntArr) wrappers.get(2)).getInts());
    Assert.assertArrayEquals(longs, ((LongArr) wrappers.get(3)).getLongs());
    Assert.assertArrayEquals(floats, ((FloatArr) wrappers.get(4)).getFloats(), 0.001f);
    Assert.assertArrayEquals(doubles, ((DoubleArr) wrappers.get(5)).getDoubles(), 0.001);
    Assert.assertArrayEquals(chars, ((CharArr) wrappers.get(6)).getChars());
    assertArrayEquals(bools, ((BooleanArr) wrappers.get(7)).getBools());
  }

  /*
   * For some reason, every other assertArrayEquals specialization is provided by org.junit.Assert,
   * but not this one.
   */
  private void assertArrayEquals(boolean[] expected, boolean[] actuals) {
    assertEquals("Arrays differ in length", expected.length, actuals.length);
    for (int i = 0; i < expected.length; ++i) {
      if (expected[i] != actuals[i]) {
        fail(
            "Arrays first differ at " + i + "; expected " + expected[i] + " but got " + actuals[i]);
      }
    }
  }

  private void tryInvalid(String t, Class<?> expectedException) {
    Yaml yaml = createYaml();
    try {
      Object loaded = yaml.load(t);
      fail("Expected " + expectedException.getCanonicalName() + " but loaded = \"" + loaded + "\"");
    } catch (YAMLException e) {
      assertEquals(expectedException, e.getCause().getClass());
    }
  }

  public void testInvalidConstructors() {
    // Loading a character as any primitive other than 'char' is a
    // NumberFormatException
    tryInvalid(pkg + ".ByteArr [ [ 'a' ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".ShortArr [ [ 'a' ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".IntArr [ [ 'a' ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".LongArr [ [ 'a' ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".FloatArr [ [ 'a' ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".DoubleArr [ [ 'a' ] ]", NumberFormatException.class);

    // Exception: because of how boolean construction works, constructing a
    // boolean from 'a'
    // results in null.
    tryInvalid(pkg + ".BooleanArr [ [ 'a' ] ]", NullPointerException.class);

    // Loading a floating-point number as a character is a YAMLException
    tryInvalid(pkg + ".CharArr [ [ 1.2 ] ]", YAMLException.class);

    // Loading a String as a Character is a YAMLException
    tryInvalid(pkg + ".CharArr [ [ 'abcd' ] ]", YAMLException.class);

  }

  public void testTruncation() {
    // Loading floating-point numbers as integer types is disallowed,
    // because that's a number-format problem.
    tryInvalid(pkg + ".ByteArr [ [ 3.14 ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".ShortArr [ [ 3.14 ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".IntArr [ [ 3.14 ] ]", NumberFormatException.class);
    tryInvalid(pkg + ".LongArr [ [ 3.14 ] ]", NumberFormatException.class);
  }

  public void testPromotion() {
    Yaml yaml = createYaml();

    // Loading integer numbers as floating-point types is allowed...
    Assert.assertArrayEquals(new float[] {3, 5},
        ((FloatArr) yaml.load(pkg + ".FloatArr [ [ 3, 5 ] ] ")).getFloats(), 0.001f);

    Assert.assertArrayEquals(new double[] {3, 5},
        ((DoubleArr) yaml.load(pkg + ".DoubleArr [ [ 3, 5 ] ] ")).getDoubles(), 0.001f);
  }

  public void testStringCharArray() {
    Yaml yaml = createYaml();

    try {
      yaml.load(pkg + ".CharArr [ [ abcd ] ]");
      fail("Expected exception.");
    } catch (Exception e) {
      assertEquals(ConstructorException.class, e.getClass());
      assertEquals("Invalid node Character: 'abcd'; length: 4", e.getCause().getMessage());
    }
  }

  private Object cycle(Object in) {
    Yaml yaml = createYaml();
    String dumped = yaml.dump(in);
    // System.out.println ( dumped );
    return yaml.load(dumped);
  }

  /**
   * All kinds of primitive arrays should be able to cycle from (Java array) to (YAML string) to
   * (Java array) again, and they should be exactly the same before and after.
   */
  public void testCycle() {
    ByteArr byteArr = new ByteArr(bytes);
    Assert.assertArrayEquals(byteArr.getBytes(), ((ByteArr) cycle(byteArr)).getBytes());

    ShortArr shortArr = new ShortArr(shorts);
    Assert.assertArrayEquals(shortArr.getShorts(), ((ShortArr) cycle(shortArr)).getShorts());

    IntArr intArr = new IntArr(ints);
    Assert.assertArrayEquals(intArr.getInts(), ((IntArr) cycle(intArr)).getInts());

    LongArr longArr = new LongArr(longs);
    Assert.assertArrayEquals(longArr.getLongs(), ((LongArr) cycle(longArr)).getLongs());

    FloatArr floatArr = new FloatArr(floats);
    Assert.assertArrayEquals(floatArr.getFloats(), ((FloatArr) cycle(floatArr)).getFloats(),
        0.001f);

    DoubleArr doubleArr = new DoubleArr(doubles);
    Assert.assertArrayEquals(doubleArr.getDoubles(), ((DoubleArr) cycle(doubleArr)).getDoubles(),
        0.001);

    CharArr charArr = new CharArr(chars);
    Assert.assertArrayEquals(charArr.getChars(), ((CharArr) cycle(charArr)).getChars());

    BooleanArr boolArr = new BooleanArr(bools);
    assertArrayEquals(boolArr.getBools(), ((BooleanArr) cycle(boolArr)).getBools());
  }

  public void testMultiDimensional() {
    Array2D two = new Array2D();
    two.setLongs(new long[][] {{1, 2, 3}, {4, 5, 6}});
    assertTrue(Arrays.deepEquals(two.getLongs(), ((Array2D) cycle(two)).getLongs()));

    Array3D three = new Array3D();
    three.setLongs(
        new long[][][] {{{1, 2, 3, 4}, {5, 6, 7, 8}}, {{9, 10, 11, 12}, {13, 14, 15, 16}}});
    assertTrue(Arrays.deepEquals(three.getLongs(), ((Array3D) cycle(three)).getLongs()));

    // Object with an array of Objects which each have an array of
    // primitives.
    ArrayLongArr four = new ArrayLongArr();
    four.setContents(
        new LongArr[] {new LongArr(new long[] {1, 2, 3, 4}), new LongArr(new long[] {5, 6, 7, 8})});
    Object result = cycle(four);
    assertEquals(four, result);
  }

  public static class Array2D {

    private long[][] longs;

    public long[][] getLongs() {
      return longs;
    }

    public void setLongs(long[][] longs) {
      this.longs = longs;
    }
  }

  public static class Array3D {

    private long[][][] longs;

    public long[][][] getLongs() {
      return longs;
    }

    public void setLongs(long[][][] longs) {
      this.longs = longs;
    }
  }

  public static class ArrayLongArr {

    private LongArr[] contents;

    public LongArr[] getContents() {
      return contents;
    }

    public void setContents(LongArr[] contents) {
      this.contents = contents;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof ArrayLongArr)) {
        return false;
      }

      ArrayLongArr other = ((ArrayLongArr) obj);
      if (contents.length != other.getContents().length) {
        return false;
      }
      for (int i = 0; i < contents.length; ++i) {
        if (!Arrays.equals(contents[i].getLongs(), other.getContents()[i].getLongs())) {
          return false;
        }
      }

      return true;
    }
  }
}
