/*
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2002-2003 (C) Intalio, Inc. All Rights Reserved.
 *
 * Portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
 * Visco. All Rights Reserverd.
 *
 * $Id: SchemaTestCase.java 6787 2007-01-29 06:00:49Z ekuns $
 *
 * Date Author Changes ----------------------------------------- 01/23/2002 Keith Visco Created
 * 03/25/2002 Arnaud Blandin Ported to CTF 10/15/2003 Arnaud Blandin Improved reporting
 */
package org.castor.xmlctf;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

import junit.framework.Test;
import junit.framework.TestSuite;

import org.castor.xmlctf.util.CTFUtils;
import org.castor.xmlctf.util.FileServices;
import org.exolab.castor.tests.framework.testDescriptor.SchemaDifferences;
import org.exolab.castor.tests.framework.testDescriptor.UnitTestCase;
import org.exolab.castor.tests.framework.testDescriptor.types.FailureStepType;
import org.exolab.castor.xml.XMLContext;
import org.exolab.castor.xml.schema.Schema;
import org.exolab.castor.xml.schema.SchemaContextImpl;
import org.exolab.castor.xml.schema.reader.SchemaReader;
import org.exolab.castor.xml.schema.writer.SchemaWriter;
import org.xml.sax.InputSource;

/**
 * A JUnit test case for testing the Castor Schema Object Model.
 *
 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
 * @version $Revision: 6787 $ $Date: 2006-04-26 15:14:53 -0600 (Wed, 26 Apr 2006) $
 */
public class SchemaTestCase extends XMLTestCase {

  /** The name of the schema to test. */
  private String _schemaName;
  /** The count of differences expected for the file-reference comparison. */
  private final int _differenceCountReference;
  /**
   * Gold file to compare a schema against. Optional. Only needed if schema changes during parsing,
   * i.e., due to redefine element.
   */
  protected final String _goldFileName;

  /**
   * Default constructor
   * 
   * @param name the name of the test
   */
  public SchemaTestCase(final String name) {
    super(name);
    throw new IllegalArgumentException("You cannot use the name-only constructor");
  } // -- SchemaTest

  /**
   * Creates a new SchemaTest with the given name
   * 
   * @param castorTc the reference to the jar/directory
   * @param tc the UnitTestCase that wraps the configuration for this XML Test case.
   */
  public SchemaTestCase(final CastorTestCase castorTc, final UnitTestCase tc) {
    super(castorTc, tc);
    _differenceCountReference = getSchemaDifferenceCount(tc, FailureStepType.COMPARE_TO_REFERENCE);
    _goldFileName = tc.getGoldFile();
  } // -- SchemaTest

  /**
   * Looks for and returns the difference count for the given step
   * 
   * @param tc the UnitTestCase that wraps the configuration for this XML Test case.
   * @param step the step to look for
   * @return the difference count for the given step
   */
  private int getSchemaDifferenceCount(final UnitTestCase tc, final FailureStepType step) {
    SchemaDifferences[] diff = tc.getSchemaDifferences();
    for (int i = 0; i < diff.length; i++) {
      if (diff[i].getFailureStep().equals(step)) {
        return diff[i].getContent();
      }
    }
    return 0;
  }

  /**
   * Sets the name of the XML schema file to test.
   *
   * @param name the name of the XML schema file to test.
   */
  public void setSchemaName(final String name) {
    _schemaName = name;
  }

  public static Test suite() {
    return new TestSuite();
  } // -- suite

  protected void setUp() throws Exception {
    verbose("\n================================================");
    verbose("Test suite '" + _test.getName() + "': setting up test '" + _name + "'");
    verbose("================================================\n");

    try {
      FileServices.copySupportFiles(_test.getTestFile(), _outputRootFile);
    } catch (IOException e) {
      fail("IOException copying support files " + e);
    }
    if (getXMLContext() == null) {
      // not wrapped inside a TestWithXy test!
      setXMLContext(new XMLContext());
    }
  }

  /**
   * Cleans up after this unit test (nothing to do except provide output).
   * 
   * @throws java.lang.Exception never
   */
  protected void tearDown() throws Exception {
    verbose("\n================================================");
    verbose("Test suite '" + _test.getName() + "': test '" + _name + "' complete.");
    verbose("================================================\n");
  }

  /**
   * Override this method to run the test and assert its state.
   *
   * @throws Throwable if any exception is thrown
   */
  public void runTest() throws Throwable {
    if (_skip) {
      verbose("-->Skipping the test");
      return;
    }

    File schemaFile = new File(_test.getTestFile() + "/" + _schemaName);
    String schemaURL = schemaFile.toURL().toString();

    Schema schema = testReadingSchema(schemaURL);
    if (schema == null) {
      return;
    }
    testWritingSchema(schemaURL, schema);

    // Compare marshaled schema to gold file if provided, otherwise to input file
    compareSchemaFiles(schemaFile);

    if (_failure != null && _failure.getContent()) {
      fail("The schema test was expected to fail, but passed");
    }
  } // -- runTest

  private void compareSchemaFiles(final File schemaFile) throws IOException {
    File file = new File(_outputRootFile,
        _schemaName.substring(0, _schemaName.lastIndexOf('.')) + "-output" + FileServices.XSD);

    String goldFileName = (_goldFileName != null) ? _outputRootFile + "/" + _goldFileName
        : schemaFile.getAbsolutePath();

    int result = CTFUtils.compare(goldFileName, file.getAbsolutePath());
    verbose("----> Compare marshaled schema to gold file '" + _goldFileName + "': "
        + ((result == 0) ? "OK" : "### Failed ### "));

    final FailureStepType step = _failure != null ? _failure.getFailureStep() : null;
    final boolean expectedToFail = _failure != null && _failure.getContent()
        && (step == null || step.equals(FailureStepType.COMPARE_TO_REFERENCE));

    if (_failure == null || !_failure.getContent()) {
      assertEquals("The Marshaled schema differs from the gold file", _differenceCountReference,
          result);
    } else if (expectedToFail) {
      assertTrue("The Marshaled schema was expected to differ from the" + " gold file, but did not",
          result != _differenceCountReference);
    }
  }

  /**
   * Reads and returns the provided XML schema.
   *
   * @param url the schema URL
   * @return the Schema that was read in
   */
  private Schema testReadingSchema(final String url) {
    verbose("--> Reading XML Schema: " + url);
    try {
      SchemaReader reader = new SchemaReader();

      reader.setSchemaContext(new SchemaContextImpl());
      reader.setInputSource(new InputSource(url));

      Schema returnValue = reader.read();
      if (_failure != null && _failure.getContent() && _failure.getFailureStep() != null
          && _failure.getFailureStep().equals(FailureStepType.PARSE_SCHEMA)) {
        fail("Reading/Parsing the schema was expected to fail, but succeeded");
      }
      return returnValue;
    } catch (Exception e) {
      if (!checkExceptionWasExpected(e, FailureStepType.PARSE_SCHEMA)) {
        fail("Unable to read Schema '" + url + "': " + e.toString());
      }
    }

    return null;
  }

  /**
   * Writes the provided schema to disk.
   *
   * @param url schema url, used only in diagnostic output
   * @param schema the schema to write
   */
  private void testWritingSchema(final String url, final Schema schema) {
    // First write the schema to disk
    try {
      String fileName =
          _schemaName.substring(0, _schemaName.lastIndexOf('.')) + "-output" + FileServices.XSD;
      verbose("--> Writing XML Schema: " + fileName);

      File output = new File(_outputRootFile, fileName);
      FileWriter writer = new FileWriter(output);

      SchemaWriter schemaWriter = new SchemaWriter();
      schemaWriter.setSchemaContext(new SchemaContextImpl());
      schemaWriter.setDocumentHandler(new PrintWriter(writer, true));

      schemaWriter.write(schema);
      writer.close();
    } catch (Exception e) {
      if (!checkExceptionWasExpected(e, FailureStepType.WRITE_SCHEMA)) {
        fail("Failed to write Schema '" + url + "' to disk: " + e.toString());
      }
      return;
    }

    if (_failure != null && _failure.getContent() && _failure.getFailureStep() != null
        && _failure.getFailureStep().equals(FailureStepType.WRITE_SCHEMA)) {
      fail("Writing the schema was expected to fail, but succeeded");
    }
  }

} // -- SchemaTest
