package com.thaiopensource.relaxng.util;

import com.thaiopensource.util.PropertyMapBuilder;
import com.thaiopensource.validate.Flag;
import com.thaiopensource.validate.SchemaReader;
import com.thaiopensource.validate.ValidationDriver;
import com.thaiopensource.validate.prop.rng.RngProperty;
import com.thaiopensource.validate.prop.schematron.SchematronProperty;
import com.thaiopensource.validate.rng.CompactSchemaReader;
import com.thaiopensource.xml.sax.ErrorHandlerImpl;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * Ant task to validate XML files using RELAX NG or other schema languages.
 */

public class JingTask extends Task {

  private File schemaFile;
  private File src;
  private final List filesets = new ArrayList();
  private PropertyMapBuilder properties = new PropertyMapBuilder();
  private boolean failOnError = true;
  private SchemaReader schemaReader = null;

  private class LogErrorHandler extends ErrorHandlerImpl {
    int logLevel = Project.MSG_ERR;

    public void warning(SAXParseException e) throws SAXParseException {
      logLevel = Project.MSG_WARN;
      super.warning(e);
    }

    public void error(SAXParseException e) {
      logLevel = Project.MSG_ERR;
      super.error(e);
    }

    public void printException(Throwable e) {
      logLevel = Project.MSG_ERR;
      super.printException(e);
    }

    public void print(String message) {
      log(message, logLevel);
    }
  }

  public JingTask() {
    RngProperty.CHECK_ID_IDREF.add(properties);
  }

  public void execute() throws BuildException {
    if (schemaFile == null)
      throw new BuildException("There must be an rngFile or schemaFile attribute",
			       getLocation());
    if (src == null && filesets.size() == 0)
      throw new BuildException("There must be a file attribute or a fileset child element",
			       getLocation());

    ErrorHandlerImpl eh = new LogErrorHandler();

    boolean hadError = false;

    try {
      ValidationDriver driver = new ValidationDriver(properties.toPropertyMap(), schemaReader);
      if (!driver.loadSchema(ValidationDriver.fileInputSource(schemaFile)))
	hadError = true;
      else {
	if (src != null) {
	  if (!driver.validate(ValidationDriver.fileInputSource(src)))
	    hadError = true;
	}
	for (int i = 0; i < filesets.size(); i++) {
	  FileSet fs = (FileSet)filesets.get(i);
	  DirectoryScanner ds = fs.getDirectoryScanner(getProject());
	  File dir = fs.getDir(getProject());
	  String[] srcs = ds.getIncludedFiles();
	  for (int j = 0; j < srcs.length; j++) {
	    if (!driver.validate(ValidationDriver.fileInputSource(new File(dir, srcs[j]))))
	      hadError = true;
	  }
	}
      }
    }
    catch (SAXException e) {
      hadError = true;
      eh.printException(e);
    }
    catch (IOException e) {
      hadError = true;
      eh.printException(e);
    }
    if (hadError && failOnError)
      throw new BuildException("Validation failed, messages should have been provided.", getLocation());
  }

  /**
   * Handles the <code>rngfile</code> attribute.
   *
   * @param rngFilename the attribute value
   */
  public void setRngfile(String rngFilename) {
    schemaFile = getProject().resolveFile(rngFilename);
  }

  /**
   * Handles the <code>schemafile</code> attribute.
   *
   * @param schemaFilename the attribute value
   */
  public void setSchemafile(String schemaFilename) {
    schemaFile = getProject().resolveFile(schemaFilename);
  }

  public void setFile(File file) {
    this.src = file;
  }

  /**
   * Handles the <code>checkid</code> attribute.
   *
   * @param checkid the attribute value converted to a boolean
   */
  public void setCheckid(boolean checkid) {
    properties.put(RngProperty.CHECK_ID_IDREF,
                   checkid ? Flag.PRESENT : null);
  }

  /**
   * Handles the <code>compactsyntax</code> attribute.
   *
   * @param compactsyntax the attribute value converted to a boolean
   */
  public void setCompactsyntax(boolean compactsyntax) {
    schemaReader = compactsyntax ? CompactSchemaReader.getInstance() : null;
  }

  /**
   * Handles the <code>feasible</code> attribute.
   *
   * @param feasible the attribute value converted to a boolean
   */
  public void setFeasible(boolean feasible) {
    properties.put(RngProperty.FEASIBLE, feasible ? Flag.PRESENT : null);
  }

  /**
   * Handles the phase attribute.
   *
   * @param phase the attribute value
   */
  public void setPhase(String phase) {
    properties.put(SchematronProperty.PHASE, phase);
  }

  /**
   * Handles the <code>failonerror</code> attribute.
   *
   * @param failOnError the attribute value converted to a boolean
   */
  public void setFailonerror(boolean failOnError) {
    this.failOnError = failOnError;
  }

  public void addFileset(FileSet set) {
    filesets.add(set);
  }

}
