/*
 * Copyright 2010 Google Inc.
 *
 * 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.gwt.core.ext.linker.impl;

import com.google.gwt.core.ext.linker.LinkerUtils;

import com.gargoylesoftware.htmlunit.AlertHandler;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.MockWebConnection;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;

import junit.framework.TestCase;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Tests the JavaScript code in the selection script test using HtmlUnit.
 */
public class SelectionScriptJavaScriptTest extends TestCase {
  private static String TEST_MODULE_NAME = "test.Module";

  /**
   * Return script code that includes the definition of computeScriptBase(),
   * most of the variables it needs to run, and a call to the function. It
   * assumes that the metaProps variable is already in scope.
   */
  private static String loadComputeScriptBase() throws IOException {
    StringBuilder code = new StringBuilder();
    code.append("var base = \"\", $doc=document;\n");
    code.append("__gwt_getMetaProperty = function(name) { "
                 + "var value = metaProps[name];"
                 + "return (value == null) ? null : value; }");
    code.append(LinkerUtils.readClasspathFileAsString(SelectionScriptLinker.COMPUTE_SCRIPT_BASE_JS));
    code.append("computeScriptBase();\n");
    return code.toString();
  }

  /**
   * Return script code that includes the definition of processMetas(), all
   * variables it needs to run, and a call to the function.
   */
  private static String loadProcessMetas() throws IOException {
    StringBuilder code = new StringBuilder();
    code.append("var metaProps = { }, propertyErrorFunc, onLoadErrorFunc;\n");
    code.append(LinkerUtils.readClasspathFileAsString(SelectionScriptLinker.PROCESS_METAS_JS));
    code.append("processMetas();\n");
    return code.toString().replaceAll("__MODULE_NAME__", TEST_MODULE_NAME);
  }

  /**
   * Test a meta tag specifying a base for this module
   */
  public void testModuleSpecificMetas1() throws IOException {
    StringBuilder metas = new StringBuilder();
    metas.append("<meta name=\"" + TEST_MODULE_NAME
        + "::gwt:property\" content=\"baseUrl=http://new/base\">\n");

    StringBuilder testCode = new StringBuilder();
    testCode.append(loadProcessMetas());
    testCode.append("alert('baseUrl='+metaProps['baseUrl']);\n");

    List<String> alerts = loadPage(makeHostPage(metas), testCode);

    assertEquals(1, alerts.size());
    assertEquals("baseUrl=http://new/base", alerts.get(0));
  }

  /**
   * Test a meta tag specifying a base for a different module.
   */
  public void testModuleSpecificMetas2() throws IOException {
    StringBuilder metas = new StringBuilder();
    metas.append("<meta name=\"some.other.module::gwt:property\" content=\"baseUrl=http://new/base\">\n");

    StringBuilder testCode = new StringBuilder();
    testCode.append(loadProcessMetas());
    testCode.append("alert('baseUrl='+metaProps['baseUrl']);\n");

    List<String> alerts = loadPage(makeHostPage(metas), testCode);

    assertEquals(1, alerts.size());
    assertEquals("baseUrl=undefined", alerts.get(0));
  }

  /**
   * Test that all the meta tags are extracted.
   */
  public void testProcessMetas() throws FailingHttpStatusCodeException,
      MalformedURLException, IOException {
    StringBuilder metas = new StringBuilder();
    metas.append("<meta name=\"gwt:property\" content=\"baseUrl=http://new/base\">\n");
    metas.append("<meta name=\"gwt:property\" content=\"novalue\">\n");
    metas.append("<meta name=\"gwt:onPropertyErrorFn\" content=\"function() { alert('custom prop error called');}\"}>\n");
    metas.append("<meta name=\"gwt:onLoadErrorFn\" content=\"function() { alert('custom onLoad error called');}\">\n");

    StringBuilder testCode = new StringBuilder();
    testCode.append(loadProcessMetas());
    testCode.append("alert('baseUrl='+metaProps['baseUrl']);\n");
    testCode.append("alert('novalue='+metaProps['novalue']);\n");
    testCode.append("alert('absent='+metaProps['absent']);\n");
    testCode.append("propertyErrorFunc();\n");
    testCode.append("onLoadErrorFunc();\n");

    List<String> alerts = loadPage(makeHostPage(metas), testCode);

    assertEquals(5, alerts.size());
    Iterator<String> alertsIter = alerts.iterator();
    assertEquals("baseUrl=http://new/base", alertsIter.next());
    assertEquals("novalue=", alertsIter.next());
    assertEquals("absent=undefined", alertsIter.next());
    assertEquals("custom prop error called", alertsIter.next());
    assertEquals("custom onLoad error called", alertsIter.next());
  }

  /**
   * Test the default href
   */
  public void testDefault() throws IOException {
    StringBuilder testCode = new StringBuilder();
    testCode.append("var metaProps = { };\n");
    testCode.append("function isBodyLoaded() { return true; }\n");
    testCode.append(loadComputeScriptBase());
    testCode.append("alert('base='+base);\n");

    List<String> alerts = loadPage(makeHostPage(""), testCode);

    assertEquals(1, alerts.size());
    assertEquals("base=http://foo.test/", alerts.get(0));
  }

  /**
   * Test the script base with no meta properties.
   */
  public void testScriptBase() throws IOException {
    StringBuilder testCode = new StringBuilder();
    testCode.append("var metaProps = { };\n");
    testCode.append("function isBodyLoaded() { return false; }\n");
    testCode.append(loadComputeScriptBase());
    testCode.append("alert('base='+base);\n");

    List<String> alerts = loadPage(makeHostPage(""), testCode);

    assertEquals(1, alerts.size());
    assertEquals("base=http://foo.test/foo/", alerts.get(0));
  }

  /**
   * Test getting the base URL from a meta property with an absolute URL
   */
  public void testScriptBaseFromMetas() throws IOException {
    StringBuilder testCode = new StringBuilder();
    testCode.append("var metaProps = { baseUrl : \"http://static.test/fromMetaTag/\" };\n");
    testCode.append(loadComputeScriptBase());
    testCode.append("alert('base='+base);\n");

    List<String> alerts = loadPage(makeHostPage(""), testCode);

    assertEquals(1, alerts.size());
    assertEquals("base=http://static.test/fromMetaTag/", alerts.get(0));
  }

  /**
   * Test getting the base URL from a meta property with a relative URL
   */
  public void testRelativeScriptBaseFromMetas() throws IOException {
    StringBuilder testCode = new StringBuilder();
    testCode.append("var metaProps = { baseUrl : \"fromMeta/tag/\" };\n");
    testCode.append(loadComputeScriptBase());
    testCode.append("alert('base='+base);\n");

    List<String> alerts = loadPage(makeHostPage(""), testCode);

    assertEquals(1, alerts.size());
    assertEquals("base=http://foo.test/fromMeta/tag/", alerts.get(0));
  }

  /**
   * Test the script base logic for an inlined selection script.
   */
  public void testScriptBaseForInlined() throws IOException {
    StringBuilder hostPage = new StringBuilder();
    hostPage.append("<html><head>\n");
    hostPage.append("<script lang=\"javascript\"><!--\n");
    hostPage.append("var metaProps = { }\n");
    hostPage.append(loadComputeScriptBase());
    hostPage.append("alert('base='+base);\n");
    hostPage.append("--></script>\n");

    List<String> alerts = loadPage(hostPage.toString(), "");

    assertEquals(1, alerts.size());
    assertEquals("base=http://foo.test/", alerts.get(0));
  }

  /**
   * Test getting a the base URL from the HTML base tag
   */
  public void testScriptBaseFromBaseTag() throws IOException {
    StringBuilder hostPage = new StringBuilder();
    hostPage.append("<html><head>\n");
    hostPage.append("<base href=\"http://static.test/fromBaseTag/\">\n");
    hostPage.append("<script lang=\"javascript\"><!--\n");
    hostPage.append("var metaProps = { }\n");
    hostPage.append(loadComputeScriptBase());
    hostPage.append("alert('base='+base);\n");
    hostPage.append("--></script>\n");

    List<String> alerts = loadPage(hostPage.toString(), "");

    assertEquals(1, alerts.size());
    assertEquals("base=http://static.test/fromBaseTag/", alerts.get(0));
  }

  /**
   * Test the script base logic for an inlined selection script.
   */
  public void testNocacheJsTag() throws IOException {
    StringBuilder hostPage = new StringBuilder();
    hostPage.append("<html><head>\n");
    hostPage.append("<script lang='javascript' type='application/javascript' ");
    hostPage.append("src='from/nocache/__MODULE_NAME__.nocache.js'></script>\n");
    hostPage.append("<script lang=\"javascript\"><!--\n");
    hostPage.append("var metaProps = { }\n");
    hostPage.append(loadComputeScriptBase());
    hostPage.append("alert('base='+base);\n");
    hostPage.append("--></script>\n");

    List<String> alerts = loadPage(hostPage.toString(), "");

    assertEquals(1, alerts.size());
    assertEquals("base=http://foo.test/from/nocache/", alerts.get(0));
  }

  /**
   * Load a page and return all alerts that it generates.
   */
  private List<String> loadPage(String hostPage, CharSequence testScript)
      throws FailingHttpStatusCodeException, MalformedURLException, IOException {
    WebClient webClient = new WebClient();

    // Make a mock web connection that can return the host page and the test
    // script
    MockWebConnection webConnection = new MockWebConnection();
    webConnection.setDefaultResponse(hostPage, "text/html");
    webConnection.setResponse(new URL(
        "http://foo.test/foo/test.Module.nocache.js"), testScript.toString(),
        "application/javascript");
    webConnection.setResponse(new URL(
            "http://foo.test/from/nocache/__MODULE_NAME__.nocache.js"),
            testScript.toString(),
            "application/javascript");
    webClient.setWebConnection(webConnection);

    final List<String> alerts = new ArrayList<String>();
    webClient.setAlertHandler(new AlertHandler() {
      @Override
      public void handleAlert(Page page, String msg) {
        alerts.add(msg);
      }
    });

    webClient.getPage("http://foo.test/");
    return alerts;
  }

  private String makeHostPage(CharSequence metas) {
    StringBuilder buf = new StringBuilder();
    buf.append("<html><head>\n");
    buf.append(metas);
    buf.append("<script src=\"/foo/test.Module.nocache.js\">\n");
    buf.append("</head></html>");
    return buf.toString();
  }
}
