/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Portions Copyright 2015 ForgeRock AS.
 */

package org.opends.server.admin;

import static org.testng.Assert.*;

import org.opends.server.DirectoryServerTestCase;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.RootCfgDefn;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

/**
 * SizePropertyDefinition Tester.
 */
public class SizePropertyDefinitionTest extends DirectoryServerTestCase {

  /**
   * Sets up tests
   *
   * @throws Exception
   *           If the server could not be initialized.
   */
  @BeforeClass
  public void setUp() throws Exception {
    // This test suite depends on having the schema available, so
    // we'll start the server.
    TestCaseUtils.startServer();
  }

  /**
   * Tests creation of builder succeeds.
   */
  @Test
  public void testCreateBuilder() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    assertNotNull(builder);
  }

  /**
   * Tests setting/getting of lower limit as long.
   */
  @Test
  public void testLowerLimit1() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setLowerLimit(1);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    assertEquals(spd.getLowerLimit(), 1);
  }

  /**
   * Creates data for testing string-based limit values.
   * @return data
   */
  @DataProvider(name = "stringLimitData")
  public Object[][] createStringLimitData() {
    return new Object[][]{
            {"1 b", 1L},
            // { null, 0 }
    };
  }

  /**
   * Creates data for testing limit values.
   * @return data
   */
  @DataProvider(name = "illegalLimitData")
  public Object[][] createIllegalLimitData() {
    return new Object[][]{
            {-1L, 0L, true}, // lower, upper, lower first
            {0L, -1L, false},
            {2L, 1L, true},
            {2L, 1L, false}
    };
  }


  /**
   * Tests setting/getting of lower limit as String.
   * @param limit unit limit
   * @param expectedValue to compare
   */
  @Test(dataProvider = "stringLimitData")
  public void testLowerLimit2(String limit, long expectedValue) {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setLowerLimit(limit);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    assertEquals(spd.getLowerLimit(), expectedValue);
  }

  /**
   * Tests setting/getting of lower limit as long.
   */
  @Test
  public void testUpperLimit1() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setLowerLimit(1);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    assertEquals(spd.getLowerLimit(), 1);
  }

  /**
   * Tests setting/getting of lower limit as String.
   * @param limit upper limit
   * @param expectedValue to compare
   */
  @Test(dataProvider = "stringLimitData")
  public void testUpperLimit2(String limit, Long expectedValue) {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setUpperLimit(limit);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    assertEquals(spd.getUpperLimit(), expectedValue);
  }

  /**
   * Tests setting/getting of lower limit as String.
   * @param upper upper limit
   * @param lower lower limit
   * @param lowerFirst when true sets the lower limit property first
   */
  @Test(dataProvider = "illegalLimitData", expectedExceptions = IllegalArgumentException.class)
  public void testIllegalLimits(long lower, long upper, boolean lowerFirst) {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    if (lowerFirst) {
      builder.setLowerLimit(lower);
      builder.setUpperLimit(upper);
    } else {
      builder.setUpperLimit(upper);
      builder.setLowerLimit(lower);
    }
  }

  /**
   * Tests the allowUnlimited property.
   */
  @Test
  public void testIsAllowUnlimited1() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(true);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    spd.decodeValue("unlimited");
  }

  /**
   * Tests the allowUnlimited property.
   */
  @Test(expectedExceptions = PropertyException.class)
  public void testIsAllowUnlimited2() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(false);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    spd.decodeValue("unlimited");
  }

  /**
   * Tests the allowUnlimited property.
   */
  @Test(expectedExceptions = PropertyException.class)
  public void testIsAllowUnlimited3() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(false);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    spd.validateValue(-1L);
  }

  /**
   * Creates illegal data for validate value.
   * @return data
   */
  @DataProvider(name = "validateValueData")
  public Object[][] createvalidateValueData() {
    return new Object[][]{
            {5L, 10L, false, 7L},
            {5L, null, true, -1L},
            {5L, 10L, true, -1L},
    };
  }

  /**
   * Tests that validateValue works.
   * @param value to validate
   * @param allowUnlimited when true allows unlimited
   * @param high upper limit
   * @param low lower limit
   */
  @Test(dataProvider = "validateValueData")
  public void testValidateValue1(Long low, Long high, boolean allowUnlimited, Long value) {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setLowerLimit(low);
    builder.setUpperLimit(high);
    builder.setAllowUnlimited(allowUnlimited);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    spd.validateValue(value);
  }

  /**
   * Creates illegal data for validate value.
   * @return data
   */
  @DataProvider(name = "illegalValidateValueData")
  public Object[][] createIllegalValidateValueData() {
    return new Object[][]{
            {5L, 10L, false, null},
            {5L, 10L, false, 1L},
            {5L, 10L, false, 11L},
            {5L, 10L, false, -1L},
            {5L, 10L, true, 2L},
            {5L, 10L, true, 11L}
    };
  }

  /**
   * Tests that validateValue throws exceptions.
   * @param value to validate
   * @param low lower limit
   * @param high upper limit
   * @param allowUnlimited when true allows unlimited
   */
  @Test(dataProvider = "illegalValidateValueData",
          expectedExceptions = {AssertionError.class,NullPointerException.class,PropertyException.class})
  public void testValidateValue2(Long low, Long high, boolean allowUnlimited, Long value) {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setLowerLimit(low);
    builder.setUpperLimit(high);
    builder.setAllowUnlimited(allowUnlimited);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    spd.validateValue(value);
  }

  /**
   * Creates encode test data.
   * @return data
   */
  @DataProvider(name = "encodeValueData")
  public Object[][] createEncodeValueData() {
    return new Object[][]{
            {-1L, "unlimited"},
            {0L, "0 b"},
            {1L, "1 b"},
            {2L, "2 b"},
            {999L, "999 b"},
            {1000L, "1 kb"},
            {1001L, "1001 b"},
            {1023L, "1023 b"},
            {1024L, "1 kib"},
            {1025L, "1025 b"},
            {1000L * 1000L, "1 mb"},
            {1000L * 1000L * 1000L, "1 gb"},
            {1024L * 1024L * 1024L, "1 gib"},
            {1000L * 1000L * 1000L * 1000L, "1 tb"}

    };
  }

  /**
   * Tests encode value.
   * @param value to encode
   * @param expectedValue to compare
   */
  @Test(dataProvider = "encodeValueData")
  public void testEncodeValue(Long value, String expectedValue) {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(true);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    assertEquals(spd.encodeValue(value), expectedValue);
  }

  /**
   * Test that accept doesn't throw and exception.
   */
  @Test
  public void testAccept() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(true);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    PropertyDefinitionVisitor<Boolean, Void> v = new PropertyDefinitionVisitor<Boolean, Void>() {

      @Override
      public Boolean visitSize(SizePropertyDefinition d,
          Void o) {
        return true;
      }

      @Override
      public Boolean visitUnknown(PropertyDefinition d, Void o)
          throws PropertyException {
        return false;
      }

    };

    assertEquals((boolean) spd.accept(v, null), true);
  }

  /**
   * Make sure toString doesn't barf.
   */
  @Test
  public void testToString() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(true);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    spd.toString();
  }

  /**
   * Test comparator method.
   */
  @Test
  public void testCompare() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(true);
    SizePropertyDefinition spd = buildTestDefinition(builder);
    assertEquals(spd.compare(1L, 2L), -1);
  }

  /**
   * Tests default behavior.
   */
  @Test
  public void testSetDefaultBehaviorProvider() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setAllowUnlimited(true);
    builder.setDefaultBehaviorProvider(new DefaultBehaviorProvider<Long>() {
      @Override
      public <R, P> R accept(DefaultBehaviorProviderVisitor<Long, R, P> v, P p) {
        return null;
      }
    });
  }

  /**
   * Tests option setting.
   */
  @Test
  public void testSetOption() {
    SizePropertyDefinition.Builder builder = createTestBuilder();
    builder.setOption(PropertyOption.HIDDEN);
  }

  private SizePropertyDefinition.Builder createTestBuilder() {
    return SizePropertyDefinition.createBuilder(RootCfgDefn.getInstance(), "test-property-name");
  }

  private SizePropertyDefinition buildTestDefinition(SizePropertyDefinition.Builder builder) {
    builder.setDefaultBehaviorProvider(new DefinedDefaultBehaviorProvider<Long>("0"));
    return builder.getInstance();
  }

}
