package blanco.xml.bind;

import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;

import blanco.xml.bind.valueobject.BlancoXmlAttribute;
import blanco.xml.bind.valueobject.BlancoXmlCdata;
import blanco.xml.bind.valueobject.BlancoXmlCharacters;
import blanco.xml.bind.valueobject.BlancoXmlComment;
import blanco.xml.bind.valueobject.BlancoXmlDocument;
import blanco.xml.bind.valueobject.BlancoXmlDtd;
import blanco.xml.bind.valueobject.BlancoXmlElement;
import blanco.xml.bind.valueobject.BlancoXmlIgnorableWhitespace;
import blanco.xml.bind.valueobject.BlancoXmlLocator;
import blanco.xml.bind.valueobject.BlancoXmlPrefixMapping;

/**
 * コンテンツハンドラ実装です。
 * 
 * このクラスは XML/バリューオブジェクトマッピング (X/Oマッピング) blancoXmlBinding の一部です。<br>
 * ※このクラスはパッケージ内でのみ利用可能です。
 * 
 * @author IGA Tosiki
 */
class BlancoXmlUnmarshallerContentHandler implements ContentHandler,
        LexicalHandler {
    /**
     * メモリ効率のためなどに文字列のキャッシュなどを実験しましたが、むしろ悪化しました。<br>
     * 現時点では小細工はせずに、処理の改善に注力することとします。
     * 
     * 効果のなかった実装は、以下のような Mapをもちいた 文字列共有化。 // private Map<String, String>
     * fStringMap = new HashMap<String, String>();
     */

    /**
     * ルートドキュメントを記憶します。
     */
    final BlancoXmlDocument fDocument = new BlancoXmlDocument();

    /**
     * 現在処理中のポイントを記憶します。
     */
    @SuppressWarnings("unchecked")
    private Stack fDocumentElementStack = new Stack();

    /**
     * コンテントハンドラオブジェクトを新規作成します。
     */
    @SuppressWarnings("unchecked")
    public BlancoXmlUnmarshallerContentHandler() {
        fDocumentElementStack.push(fDocument);
    }

    /**
     * ルートドキュメントを取得します。
     * 
     * @return
     */
    public BlancoXmlDocument getDocument() {
        return fDocument;
    }

    // ------------------------------------------------
    // ここからは ContentHandler のためのメソッド。
    // ------------------------------------------------

    public void startDocument() throws SAXException {
        // 既にpush済みです。
    }

    public void endDocument() throws SAXException {
        fDocumentElementStack.pop();
    }

    public void setDocumentLocator(final Locator argLocator) {
        final BlancoXmlLocator locator = new BlancoXmlLocator();

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlDocument == false) {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: Documentではないものに対して Locatorをセットしようとしました。");
        }

        ((BlancoXmlDocument) objCurrent).setLocator(locator);

        locator.setPublicId(argLocator.getPublicId());
        locator.setSystemId(argLocator.getSystemId());
        locator.setLineNumber(argLocator.getLineNumber());
        locator.setColumnNumber(argLocator.getColumnNumber());
    }

    public void startPrefixMapping(final String prefix, final String uri)
            throws SAXException {
        final BlancoXmlPrefixMapping prefixMapping = new BlancoXmlPrefixMapping();
        prefixMapping.setPrefix(prefix);
        prefixMapping.setUri(uri);

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlDocument == false) {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: Documentではないものに対して prefixMappingをセットしようとしました。");
        }

        ((BlancoXmlDocument) objCurrent).getPrefixMappings().add(prefixMapping);
    }

    public void endPrefixMapping(final String prefix) throws SAXException {
    }

    @SuppressWarnings("unchecked")
    public void startElement(final String uri, final String localName,
            final String qName, final Attributes atts) throws SAXException {
        final BlancoXmlElement element = new BlancoXmlElement();

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlElement) {
            final BlancoXmlElement elementParent = ((BlancoXmlElement) objCurrent);
            elementParent.getChildNodes().add(element);
        } else if (objCurrent instanceof BlancoXmlDocument) {
            ((BlancoXmlDocument) objCurrent).getChildNodes().add(element);
        } else {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: 想定されない型["
                            + objCurrent.getClass().getName()
                            + "]に対して Elementを追加しようとしました。");
        }

        // エレメントをコピーします。
        element.setUri(uri);
        element.setLocalName(localName);
        element.setQName(qName);

        // アトリビュートのコピーを行います。
        copyAttributes(atts, element);

        fDocumentElementStack.push(element);
    }

    /**
     * アトリビュートのコピーを行います。
     * 
     * @param atts
     *            入力となるアトリビュート。
     * @param element
     *            複写先のエレメント。
     */
    private void copyAttributes(final Attributes atts,
            final BlancoXmlElement element) {
        final int attrLength = atts.getLength();
        for (int index = 0; index < attrLength; index++) {
            final BlancoXmlAttribute attribute = new BlancoXmlAttribute();
            element.getAtts().add(attribute);

            attribute.setUri(atts.getURI(index));
            attribute.setLocalName(atts.getLocalName(index));
            attribute.setQName(atts.getQName(index));
            attribute.setType(atts.getType(index));
            attribute.setValue(atts.getValue(index));
        }
    }

    public void endElement(final String uri, final String localName,
            final String qName) throws SAXException {
        fDocumentElementStack.pop();
    }

    public void characters(final char[] ch, final int start, final int length)
            throws SAXException {
        final BlancoXmlCharacters characters = new BlancoXmlCharacters();

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlElement) {
            ((BlancoXmlElement) objCurrent).getChildNodes().add(characters);
        } else if (objCurrent instanceof BlancoXmlCdata) {
            ((BlancoXmlCdata) objCurrent).getChildNodes().add(characters);
        } else if (objCurrent instanceof BlancoXmlDocument) {
            ((BlancoXmlDocument) objCurrent).getChildNodes().add(characters);
        } else {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: 想定されない型["
                            + objCurrent.getClass().getName()
                            + "]に対して Charactersを追加しようとしました。");
        }

        characters.setValue(new String(ch, start, length));
    }

    public void ignorableWhitespace(final char[] ch, final int start,
            final int length) throws SAXException {
        final BlancoXmlIgnorableWhitespace ignorableWhitespace = new BlancoXmlIgnorableWhitespace();

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlElement) {
            ((BlancoXmlElement) objCurrent).getChildNodes().add(
                    ignorableWhitespace);
        } else if (objCurrent instanceof BlancoXmlCdata) {
            ((BlancoXmlDocument) objCurrent).getChildNodes().add(
                    ignorableWhitespace);
        } else if (objCurrent instanceof BlancoXmlDocument) {
            ((BlancoXmlDocument) objCurrent).getChildNodes().add(
                    ignorableWhitespace);
        } else {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: 想定されない型["
                            + objCurrent.getClass().getName()
                            + "]に対して BlancoXmlIgnorableWhitespaceを追加しようとしました。");
        }

        ignorableWhitespace.setValue(new String(ch, start, length));
    }

    public void processingInstruction(final String target, final String data)
            throws SAXException {
        // このイベントは現時点での仕様では無視します。
    }

    public void skippedEntity(final String name) throws SAXException {
        // このイベントは現時点での仕様では無視します。
    }

    // ------------------------------------------------
    // ここからは LexicalHandler のためのメソッド。
    // ------------------------------------------------

    public void startDTD(final String name, final String publicId,
            final String systemId) throws SAXException {
        final BlancoXmlDtd dtd = new BlancoXmlDtd();

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlDocument) {
            ((BlancoXmlDocument) objCurrent).getChildNodes().add(dtd);
        } else {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: 想定されない型["
                            + objCurrent.getClass().getName()
                            + "]に対して BlancoXmlDtdを追加しようとしました。");
        }
    }

    public void endDTD() throws SAXException {
    }

    public void startEntity(final String name) throws SAXException {
        // 現在の仕様では、サポート外です。
    }

    public void endEntity(final String name) throws SAXException {
        // 現在の仕様では、サポート外です。
    }

    @SuppressWarnings("unchecked")
    public void startCDATA() throws SAXException {
        final BlancoXmlCdata cdata = new BlancoXmlCdata();

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlElement) {
            final BlancoXmlElement elementParent = ((BlancoXmlElement) objCurrent);
            elementParent.getChildNodes().add(cdata);
        } else if (objCurrent instanceof BlancoXmlDocument) {
            ((BlancoXmlDocument) objCurrent).getChildNodes().add(cdata);
        } else {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: 想定されない型["
                            + objCurrent.getClass().getName()
                            + "]に対して CDATAを追加しようとしました。");
        }

        fDocumentElementStack.push(cdata);
    }

    public void endCDATA() throws SAXException {
        fDocumentElementStack.pop();
    }

    public void comment(final char[] ch, final int start, final int length)
            throws SAXException {
        final BlancoXmlComment comment = new BlancoXmlComment();

        // 現在処理中のオブジェクトを取得します。
        final Object objCurrent = fDocumentElementStack.peek();
        if (objCurrent instanceof BlancoXmlElement) {
            ((BlancoXmlElement) objCurrent).getChildNodes().add(comment);
        } else if (objCurrent instanceof BlancoXmlDocument) {
            ((BlancoXmlDocument) objCurrent).getChildNodes().add(comment);
        } else {
            throw new IllegalArgumentException(
                    "BlancoXmlUnmarshallerContentHandler: 想定されない型["
                            + objCurrent.getClass().getName()
                            + "]に対して BlancoXmlCommentを追加しようとしました。");
        }

        comment.setValue(new String(ch, start, length));
    }
}
