/*
 * Copyright (C) 2009 The Guava Authors
 *
 * 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 com.google.common.net;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Ascii;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.testing.EqualsTester;
import com.google.common.testing.NullPointerTester;
import junit.framework.TestCase;

/**
 * {@link TestCase} for {@link InternetDomainName}.
 *
 * @author Craig Berry
 */
@GwtCompatible(emulated = true)
public final class InternetDomainNameTest extends TestCase {
  private static final InternetDomainName UNICODE_EXAMPLE =
      InternetDomainName.from("j\u00f8rpeland.no");
  private static final InternetDomainName PUNYCODE_EXAMPLE =
      InternetDomainName.from("xn--jrpeland-54a.no");

  /**
   * The Greek letter delta, used in unicode testing.
   */
  private static final String DELTA = "\u0394";

  /**
   * A domain part which is valid under lenient validation, but invalid under
   * strict validation.
   */
  static final String LOTS_OF_DELTAS = Strings.repeat(DELTA, 62);

  private static final String ALMOST_TOO_MANY_LEVELS =
      Strings.repeat("a.", 127);

  private static final String ALMOST_TOO_LONG =
      Strings.repeat("aaaaa.", 40) + "1234567890.c";

  private static final ImmutableSet<String> VALID_NAME = ImmutableSet.of(
      "foo.com",
      "f-_-o.cOM",
      "f--1.com",
      "f11-1.com",
      "www",
      "abc.a23",
      "biz.com.ua",
      "x",
      "fOo",
      "f--o",
      "f_a",
      "foo.net.us\uFF61ocm",
      "woo.com.",
      "a" + DELTA + "b.com",
      ALMOST_TOO_MANY_LEVELS,
      ALMOST_TOO_LONG);

  private static final ImmutableSet<String> INVALID_NAME = ImmutableSet.of(
      "",
      " ",
      "127.0.0.1",
      "::1", "13",
      "abc.12c",
      "foo-.com",
      "_bar.quux",
      "foo+bar.com",
      "foo!bar.com",
      ".foo.com",
      "..bar.com",
      "baz..com",
      "..quiffle.com",
      "fleeb.com..",
      ".",
      "..",
      "...",
      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com",
      "a" + DELTA + " .com",
      ALMOST_TOO_MANY_LEVELS + "com",
      ALMOST_TOO_LONG + ".c");

  private static final ImmutableSet<String> PS = ImmutableSet.of(
      "com",
      "co.uk",
      "foo.bd",
      "xxxxxx.bd",
      "org.mK",
      "us",
      "uk\uFF61com.",  // Alternate dot character
      "\u7f51\u7edc.Cn",  // "网络.Cn"
      "j\u00f8rpeland.no",  // "jorpeland.no" (first o slashed)
      "xn--jrpeland-54a.no"  // IDNA (punycode) encoding of above
  );

  private static final ImmutableSet<String> NO_PS = ImmutableSet.of(
      "www", "foo.ihopethiswillneverbeapublicsuffix", "x.y.z");

  private static final ImmutableSet<String> NON_PS = ImmutableSet.of(
      "foo.bar.com", "foo.ca", "foo.bar.ca",
      "foo.bar.co.il", "state.CA.us", "www.state.pa.us", "pvt.k12.ca.us",
      "www.google.com", "www4.yahoo.co.uk", "home.netscape.com",
      "web.MIT.edu", "foo.eDu.au", "utenti.blah.IT", "dominio.com.co");

  private static final ImmutableSet<String> TOP_PRIVATE_DOMAIN =
      ImmutableSet.of("google.com", "foo.Co.uk", "foo.ca.us.");

  private static final ImmutableSet<String> UNDER_PRIVATE_DOMAIN =
      ImmutableSet.of("foo.bar.google.com", "a.b.co.uk", "x.y.ca.us");

  private static final ImmutableSet<String> VALID_IP_ADDRS = ImmutableSet.of(
      "1.2.3.4", "127.0.0.1", "::1", "2001:db8::1");

  private static final ImmutableSet<String> INVALID_IP_ADDRS = ImmutableSet.of(
      "", "1", "1.2.3", "...", "1.2.3.4.5", "400.500.600.700",
      ":", ":::1", "2001:db8:");

  private static final ImmutableSet<String> SOMEWHERE_UNDER_PS =
      ImmutableSet.of(
          "foo.bar.google.com",
          "a.b.c.1.2.3.ca.us",
          "site.jp",
          "uomi-online.kir.jp",
          "jprs.co.jp",
          "site.quick.jp",
          "site.tenki.jp",
          "site.or.jp",
          "site.gr.jp",
          "site.ne.jp",
          "site.ac.jp",
          "site.ad.jp",
          "site.ed.jp",
          "site.geo.jp",
          "site.go.jp",
          "site.lg.jp",
          "1.fm",
          "site.cc",
          "site.ee",
          "site.fi",
          "site.fm",
          "site.gr",
          "www.leguide.ma",
          "site.ma",
          "some.org.mk",
          "site.mk",
          "site.tv",
          "site.us",
          "www.odev.us",
          "www.GOOGLE.com",
          "www.com",
          "google.com",
          "www7.google.co.uk",
          "google.Co.uK",
          "jobs.kt.com.",
          "home.netscape.com",
          "web.stanford.edu",
          "stanford.edu",
          "state.ca.us",
          "www.state.ca.us",
          "state.ca.us",
          "pvt.k12.ca.us",
          "www.rave.ca.",
          "cnn.ca",
          "ledger-enquirer.com",
          "it-trace.ch",
          "cool.dk",
          "cool.co.uk",
          "cool.de",
          "cool.es",
          "cool\uFF61fr", // Alternate dot character
          "cool.nl",
          "members.blah.nl.",
          "cool.se",
          "utenti.blah.it",
          "kt.co",
          "a\u7f51\u7edcA.\u7f51\u7edc.Cn"  // "a网络A.网络.Cn"
      );

  public void testValid() {
    for (String name : VALID_NAME) {
      InternetDomainName.from(name);
    }
  }

  public void testInvalid() {
    for (String name : INVALID_NAME) {
      try {
        InternetDomainName.from(name);
        fail("Should have been invalid: '" + name + "'");
      } catch (IllegalArgumentException expected) {
        // Expected case
      }
    }
  }

  public void testPublicSuffix() {
    for (String name : PS) {
      final InternetDomainName domain = InternetDomainName.from(name);
      assertTrue(name, domain.isPublicSuffix());
      assertTrue(name, domain.hasPublicSuffix());
      assertFalse(name, domain.isUnderPublicSuffix());
      assertFalse(name, domain.isTopPrivateDomain());
      assertEquals(domain, domain.publicSuffix());
    }

    for (String name : NO_PS) {
      final InternetDomainName domain = InternetDomainName.from(name);
      assertFalse(name, domain.isPublicSuffix());
      assertFalse(name, domain.hasPublicSuffix());
      assertFalse(name, domain.isUnderPublicSuffix());
      assertFalse(name, domain.isTopPrivateDomain());
      assertNull(domain.publicSuffix());
    }

    for (String name : NON_PS) {
      final InternetDomainName domain = InternetDomainName.from(name);
      assertFalse(name, domain.isPublicSuffix());
      assertTrue(name, domain.hasPublicSuffix());
      assertTrue(name, domain.isUnderPublicSuffix());
    }
  }

  public void testUnderPublicSuffix() {
    for (String name : SOMEWHERE_UNDER_PS) {
      final InternetDomainName domain = InternetDomainName.from(name);
      assertFalse(name, domain.isPublicSuffix());
      assertTrue(name, domain.hasPublicSuffix());
      assertTrue(name, domain.isUnderPublicSuffix());
    }
  }

  public void testTopPrivateDomain() {
    for (String name : TOP_PRIVATE_DOMAIN) {
      final InternetDomainName domain = InternetDomainName.from(name);
      assertFalse(name, domain.isPublicSuffix());
      assertTrue(name, domain.hasPublicSuffix());
      assertTrue(name, domain.isUnderPublicSuffix());
      assertTrue(name, domain.isTopPrivateDomain());
      assertEquals(domain.parent(), domain.publicSuffix());
    }
  }

  public void testUnderPrivateDomain() {
    for (String name : UNDER_PRIVATE_DOMAIN) {
      final InternetDomainName domain = InternetDomainName.from(name);
      assertFalse(name, domain.isPublicSuffix());
      assertTrue(name, domain.hasPublicSuffix());
      assertTrue(name, domain.isUnderPublicSuffix());
      assertFalse(name, domain.isTopPrivateDomain());
    }
  }

  public void testParent() {
    assertEquals(
        "com",
        InternetDomainName.from("google.com").parent().toString());
    assertEquals(
        "uk",
        InternetDomainName.from("co.uk").parent().toString());
    assertEquals(
        "google.com",
        InternetDomainName.from("www.google.com").parent().toString());

    try {
      InternetDomainName.from("com").parent();
      fail("'com' should throw ISE on .parent() call");
    } catch (IllegalStateException expected) {
    }
  }

  public void testChild() {
    InternetDomainName domain = InternetDomainName.from("foo.com");

    assertEquals("www.foo.com", domain.child("www").toString());

    try {
      domain.child("www.");
      fail("www..google.com should have been invalid");
    } catch (IllegalArgumentException expected) {
      // Expected outcome
    }
  }

  public void testParentChild() {
    InternetDomainName origin = InternetDomainName.from("foo.com");
    InternetDomainName parent = origin.parent();
    assertEquals("com", parent.toString());

    // These would throw an exception if leniency were not preserved during parent() and child()
    // calls.
    InternetDomainName child = parent.child(LOTS_OF_DELTAS);
    child.child(LOTS_OF_DELTAS);
  }

  public void testValidTopPrivateDomain() {
    InternetDomainName googleDomain = InternetDomainName.from("google.com");

    assertEquals(googleDomain, googleDomain.topPrivateDomain());
    assertEquals(googleDomain, googleDomain.child("mail").topPrivateDomain());
    assertEquals(googleDomain, googleDomain.child("foo.bar").topPrivateDomain());
  }

  public void testInvalidTopPrivateDomain() {
    ImmutableSet<String> badCookieDomains =
        ImmutableSet.of("co.uk", "foo", "com");

    for (String domain : badCookieDomains) {
      try {
        InternetDomainName.from(domain).topPrivateDomain();
        fail(domain);
      } catch (IllegalStateException expected) {
      }
    }
  }

  public void testIsValid() {
    final Iterable<String> validCases = Iterables.concat(
        VALID_NAME, PS, NO_PS, NON_PS);
    final Iterable<String> invalidCases = Iterables.concat(
        INVALID_NAME, VALID_IP_ADDRS, INVALID_IP_ADDRS);

    for (String valid : validCases) {
      assertTrue(valid, InternetDomainName.isValid(valid));
    }

    for (String invalid : invalidCases) {
      assertFalse(invalid, InternetDomainName.isValid(invalid));
    }
  }

  public void testToString() {
    for (String inputName : SOMEWHERE_UNDER_PS) {
      InternetDomainName domain = InternetDomainName.from(inputName);

      /*
       * We would ordinarily use constants for the expected results, but
       * doing it by derivation allows us to reuse the test case definitions
       * used in other tests.
       */

      String expectedName = Ascii.toLowerCase(inputName);
      expectedName = expectedName.replaceAll("[\u3002\uFF0E\uFF61]", ".");

      if (expectedName.endsWith(".")) {
        expectedName = expectedName.substring(0, expectedName.length() - 1);
      }

      assertEquals(expectedName, domain.toString());
    }
  }

  public void testExclusion() {
    InternetDomainName domain = InternetDomainName.from("foo.teledata.mz");
    assertTrue(domain.hasPublicSuffix());
    assertEquals("mz", domain.publicSuffix().toString());

    // Behold the weirdness!
    assertFalse(domain.publicSuffix().isPublicSuffix());
  }

  public void testMultipleUnders() {
    // PSL has both *.uk and *.sch.uk; the latter should win.
    // See http://code.google.com/p/guava-libraries/issues/detail?id=1176

    InternetDomainName domain = InternetDomainName.from("www.essex.sch.uk");
    assertTrue(domain.hasPublicSuffix());
    assertEquals("essex.sch.uk", domain.publicSuffix().toString());
    assertEquals("www.essex.sch.uk", domain.topPrivateDomain().toString());
  }

  public void testEquality() {
    new EqualsTester()
        .addEqualityGroup(
            idn("google.com"), idn("google.com"), idn("GOOGLE.COM"))
        .addEqualityGroup(idn("www.google.com"))
        .addEqualityGroup(UNICODE_EXAMPLE)
        .addEqualityGroup(PUNYCODE_EXAMPLE)
        .testEquals();
  }

  private static InternetDomainName idn(String domain) {
    return InternetDomainName.from(domain);
  }

  @GwtIncompatible // NullPointerTester
  public void testNulls() {
    final NullPointerTester tester = new NullPointerTester();

    tester.testAllPublicStaticMethods(InternetDomainName.class);
    tester.testAllPublicInstanceMethods(InternetDomainName.from("google.com"));
  }
}
