package com.jclark.xsl.sax;

import com.jclark.xsl.tr.*;
import com.jclark.xsl.om.*;
import org.xml.sax.*;
import java.io.IOException;
import java.net.URL;

public abstract class ResultBase implements Result, AttributeList {

  private DocumentHandler documentHandler;
  private CommentHandler commentHandler;
  ErrorHandler errorHandler;
  private RawCharactersHandler rawCharactersHandler;
  static private final int INITIAL_BUF_SIZE = 8192;
  private char[] buf = new char[INITIAL_BUF_SIZE];
  private int bufUsed = 0;
  private Name[] attributeNames = new Name[10];
  private String[] attributeValues = new String[10];
  private int nAttributes;
  private Name pendingElementType;
  private NamespacePrefixMap pendingNamespacePrefixMap;
  OutputMethodHandler outputMethodHandler;

  ResultBase(OutputMethodHandler outputMethodHandler, ErrorHandler errorHandler) {
    this.outputMethodHandler = outputMethodHandler;
    this.documentHandler = null;
    this.errorHandler = errorHandler;
  }

  ResultBase(DocumentHandler documentHandler, ErrorHandler errorHandler) {
    this.outputMethodHandler = null;
    this.errorHandler = errorHandler;
    setDocumentHandler(documentHandler);
  }
  
  private void setDocumentHandler(DocumentHandler handler) {
    documentHandler = handler;
    if (handler instanceof CommentHandler)
      commentHandler = (CommentHandler)handler;
    else
      commentHandler = null;
    if (handler instanceof RawCharactersHandler)
      rawCharactersHandler = (RawCharactersHandler)handler;
    else
      rawCharactersHandler = null;
  }

  public void flush() throws XSLException {
    if (pendingElementType != null) {
      startElementContent(pendingElementType, pendingNamespacePrefixMap);
      pendingElementType = null;
    }
    else if (bufUsed > 0) {
      try {
	documentHandler.characters(buf, 0, bufUsed);
	bufUsed = 0;
      }
      catch (SAXException e) {
	throwXSLException(e);
      }
    }
  }

  public void rawCharacters(String str) throws XSLException {
    if (rawCharactersHandler == null)
      characters(str);
    else {
      flush();
      try {
	rawCharactersHandler.rawCharacters(str);
      }
      catch (SAXException e) {
	throwXSLException(e);
      }
    }
  }

  public void characters(String str) throws XSLException {
    if (pendingElementType != null)
      flush();
    int strLength = str.length();
    if (bufUsed + strLength > buf.length) {
      char[] oldBuf = buf;
      int newLen = oldBuf.length * 2;
      while (newLen < bufUsed + strLength)
	newLen *= 2;
      buf = new char[newLen];
      if (bufUsed > 0)
	System.arraycopy(oldBuf, 0, buf, 0, bufUsed);
    }
    str.getChars(0, strLength, buf, bufUsed);
    bufUsed += strLength;
  }

  public void comment(String str) throws XSLException {
    if (commentHandler != null) {
      flush();
      try {
	commentHandler.comment(fixComment(str));
      }
      catch (SAXException e) {
	throwXSLException(e);
      }
    }
  }

  private static final String fixComment(String str) {
    int i = str.indexOf('-');
    while (i++ >= 0) {
      int len = str.length();
      if (i == len)
	return str + " ";
      if (str.charAt(i) == '-')
	str = str.substring(0, i) + " " + str.substring(i);
      i = str.indexOf('-', i);
    }
    return str;
  }

  public void processingInstruction(String target, String data)
    throws XSLException {
    try {
      flush();
      documentHandler.processingInstruction(target,
					    fixProcessingInstruction(data));
    }
    catch (SAXException e) {
      throwXSLException(e);
    }
  }

  private static final String fixProcessingInstruction(String str) {
    int i = str.indexOf('?');
    while (i++ >= 0) {
      int len = str.length();
      if (i == len)
	break;
      if (str.charAt(i) == '>')
	str = str.substring(0, i) + " " + str.substring(i);
      i = str.indexOf('?', i);
    }
    return str;
  }

  public void startElement(Name elementType, NamespacePrefixMap nsMap) 
    throws XSLException {
    flush();
    pendingElementType = elementType;
    pendingNamespacePrefixMap = nsMap;
    nAttributes = 0;
  }

  public void endElement(Name elementType) throws XSLException {
    flush();
    endElementContent(elementType);
  }

  protected final DocumentHandler getDocumentHandler() {
    return documentHandler;
  }

  public int getLength() {
    return nAttributes;
  }

  protected final Name getAttributeName(int i) {
    return attributeNames[i];
  }

  public String getValue(int i) {
    return attributeValues[i];
  }

  public String getType(int i) {
    return "CDATA";
  }

  public String getType(String name) {
    return "CDATA";
  }

  public String getValue(String name) {
    int len = getLength();
    for (int i = 0; i < len; i++) {
      if (name.equals(getName(i)))
	return getValue(i);
    }
    return null;
  }

  protected abstract void startElementContent(Name elementType,
					      NamespacePrefixMap nsMap)
    throws XSLException;

  protected abstract void endElementContent(Name elementType)
    throws XSLException;

  public void attribute(Name name, String value) throws XSLException {
    if (pendingElementType == null)
      return;
    for (int i = 0; i < nAttributes; i++)
      if (attributeNames[i].equals(name)) {
	attributeValues[i] = value;
	return;
      }
    if (nAttributes == attributeNames.length) {
      attributeNames = grow(attributeNames);
      attributeValues = grow(attributeValues);
    }
    attributeNames[nAttributes] = name;
    attributeValues[nAttributes] = value;
    nAttributes++;
  }

  static String[] grow(String[] v) {
    String[] old = v;
    v = new String[old.length * 2];
    System.arraycopy(old, 0, v, 0, old.length);
    return v;
  }

  static Name[] grow(Name[] v) {
    Name[] old = v;
    v = new Name[old.length * 2];
    System.arraycopy(old, 0, v, 0, old.length);
    return v;
  }

  public void start(OutputMethod outputMethod) throws XSLException {
    try {
      if (documentHandler == null) {
	Name name = outputMethod.getName();
	if (name == null)
	  setDocumentHandler(new OutputMethodDefaulter(this, outputMethod));
	else
	  setOutputMethod(name, outputMethod);
      }
      documentHandler.startDocument();
    }
    catch (IOException e) {
      throw new XSLException(e);
    }
    catch (SAXException e) {
      throwXSLException(e);
    }
  }

  DocumentHandler setOutputMethod(Name name, OutputMethod method) throws IOException, SAXException {
    String nameString;
    if (name.getNamespace() != null)
      nameString = (name.getNamespace()
		    + OutputMethodHandler.namespaceSeparator
		    + name.getLocalPart());
    else
      nameString = name.getLocalPart();
    setDocumentHandler(outputMethodHandler
		       .createDocumentHandler(nameString,
					      new OutputMethodAttributeList(method)));
    return documentHandler;
  }

  public void end() throws XSLException {
    try {
      flush();
      documentHandler.endDocument();
    }
    catch (SAXException e) {
      throwXSLException(e);
    }
  }

  protected void throwXSLException(SAXException e) throws XSLException {
    Exception wrapped = e.getException();
    if (wrapped != null)
      throw new XSLException(wrapped);
    else
      throw new XSLException(e.getMessage());
  }

  public abstract void resultTreeFragment(ResultTreeFragment frag) throws XSLException;

  public void message(Node node, String str) throws XSLException {
    if (errorHandler != null) {
      String systemId = null;
      int lineNumber = -1;
      if (node != null) {
	URL url = node.getURL();
	if (url != null)
	  systemId = url.toString();
	lineNumber = node.getLineNumber();
      }
      try {
	errorHandler.warning(new SAXParseException(str, null, systemId, lineNumber, -1, null));
      }
      catch (SAXException e) {
	throwXSLException(e);
      }
    }
  }
}
