/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You 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 org.zkoss.poi.hwpf.converter;

import org.zkoss.poi.util.Beta;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

@Beta
public class FoDocumentFacade
{
    private static final String NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";

    private static final String NS_XSLFO = "http://www.w3.org/1999/XSL/Format";

    protected final Element declarations;
    protected final Document document;
    protected final Element layoutMasterSet;
    protected Element propertiesRoot;
    protected final Element root;

    public FoDocumentFacade( Document document )
    {
        this.document = document;

        root = document.createElementNS( NS_XSLFO, "fo:root" );
        document.appendChild( root );

        layoutMasterSet = document.createElementNS( NS_XSLFO,
                "fo:layout-master-set" );
        root.appendChild( layoutMasterSet );

        declarations = document.createElementNS( NS_XSLFO, "fo:declarations" );
        root.appendChild( declarations );
    }

    public Element addFlowToPageSequence( final Element pageSequence,
            String flowName )
    {
        final Element flow = document.createElementNS( NS_XSLFO, "fo:flow" );
        flow.setAttribute( "flow-name", flowName );
        pageSequence.appendChild( flow );

        return flow;
    }

    public Element addListItem( Element listBlock )
    {
        Element result = createListItem();
        listBlock.appendChild( result );
        return result;
    }

    public Element addListItemBody( Element listItem )
    {
        Element result = createListItemBody();
        listItem.appendChild( result );
        return result;
    }

    public Element addListItemLabel( Element listItem, String text )
    {
        Element result = createListItemLabel( text );
        listItem.appendChild( result );
        return result;
    }

    public void addPageSequence( Element pageSequence )
    {
        root.appendChild( pageSequence );
    }

    public Element addPageSequence( String pageMaster )
    {
        final Element pageSequence = createPageSequence( pageMaster );
        root.appendChild( pageSequence );
        return pageSequence;
    }

    public Element addRegionBody( Element pageMaster )
    {
        final Element regionBody = document.createElementNS( NS_XSLFO,
                "fo:region-body" );
        pageMaster.appendChild( regionBody );

        return regionBody;
    }

    public Element addSimplePageMaster( String masterName )
    {
        final Element simplePageMaster = document.createElementNS( NS_XSLFO,
                "fo:simple-page-master" );
        simplePageMaster.setAttribute( "master-name", masterName );
        layoutMasterSet.appendChild( simplePageMaster );

        return simplePageMaster;
    }

    public Element createBasicLinkExternal( String externalDestination )
    {
        final Element basicLink = document.createElementNS( NS_XSLFO,
                "fo:basic-link" );
        basicLink.setAttribute( "external-destination", externalDestination );
        return basicLink;
    }

    public Element createBasicLinkInternal( String internalDestination )
    {
        final Element basicLink = document.createElementNS( NS_XSLFO,
                "fo:basic-link" );
        basicLink.setAttribute( "internal-destination", internalDestination );
        return basicLink;
    }

    public Element createBlock()
    {
        return document.createElementNS( NS_XSLFO, "fo:block" );
    }

    public Element createExternalGraphic( String source )
    {
        Element result = document.createElementNS( NS_XSLFO,
                "fo:external-graphic" );
        result.setAttribute( "src", "url('" + source + "')" );
        return result;
    }

    public Element createFootnote()
    {
        return document.createElementNS( NS_XSLFO, "fo:footnote" );
    }

    public Element createFootnoteBody()
    {
        return document.createElementNS( NS_XSLFO, "fo:footnote-body" );
    }

    public Element createInline()
    {
        return document.createElementNS( NS_XSLFO, "fo:inline" );
    }

    public Element createLeader()
    {
        return document.createElementNS( NS_XSLFO, "fo:leader" );
    }

    public Element createListBlock()
    {
        return document.createElementNS( NS_XSLFO, "fo:list-block" );
    }

    public Element createListItem()
    {
        return document.createElementNS( NS_XSLFO, "fo:list-item" );
    }

    public Element createListItemBody()
    {
        return document.createElementNS( NS_XSLFO, "fo:list-item-body" );
    }

    public Element createListItemLabel( String text )
    {
        Element result = document.createElementNS( NS_XSLFO,
                "fo:list-item-label" );
        Element block = createBlock();
        block.appendChild( document.createTextNode( text ) );
        result.appendChild( block );
        return result;
    }

    public Element createPageSequence( String pageMaster )
    {
        final Element pageSequence = document.createElementNS( NS_XSLFO,
                "fo:page-sequence" );
        pageSequence.setAttribute( "master-reference", pageMaster );
        return pageSequence;
    }

    public Element createTable()
    {
        return document.createElementNS( NS_XSLFO, "fo:table" );
    }

    public Element createTableBody()
    {
        return document.createElementNS( NS_XSLFO, "fo:table-body" );
    }

    public Element createTableCell()
    {
        return document.createElementNS( NS_XSLFO, "fo:table-cell" );
    }

    public Element createTableColumn()
    {
        return document.createElementNS( NS_XSLFO, "fo:table-column" );
    }

    public Element createTableHeader()
    {
        return document.createElementNS( NS_XSLFO, "fo:table-header" );
    }

    public Element createTableRow()
    {
        return document.createElementNS( NS_XSLFO, "fo:table-row" );
    }

    public Text createText( String data )
    {
        return document.createTextNode( data );
    }

    public Document getDocument()
    {
        return document;
    }

    protected Element getOrCreatePropertiesRoot()
    {
        if ( propertiesRoot != null )
            return propertiesRoot;

        // See http://xmlgraphics.apache.org/fop/0.95/metadata.html

        Element xmpmeta = document.createElementNS( "adobe:ns:meta/",
                "x:xmpmeta" );
        declarations.appendChild( xmpmeta );

        Element rdf = document.createElementNS( NS_RDF, "rdf:RDF" );
        xmpmeta.appendChild( rdf );

        propertiesRoot = document.createElementNS( NS_RDF, "rdf:Description" );
        propertiesRoot.setAttributeNS( NS_RDF, "rdf:about", "" );
        rdf.appendChild( propertiesRoot );

        return propertiesRoot;
    }

    public void setCreator( String value )
    {
        setDublinCoreProperty( "creator", value );
    }

    public void setCreatorTool( String value )
    {
        setXmpProperty( "CreatorTool", value );
    }

    public void setDescription( String value )
    {
        Element element = setDublinCoreProperty( "description", value );

        if ( element != null )
        {
            element.setAttributeNS( "http://www.w3.org/XML/1998/namespace",
                    "xml:lang", "x-default" );
        }
    }

    public Element setDublinCoreProperty( String name, String value )
    {
        return setProperty( "http://purl.org/dc/elements/1.1/", "dc", name,
                value );
    }

    public void setKeywords( String value )
    {
        setPdfProperty( "Keywords", value );
    }

    public Element setPdfProperty( String name, String value )
    {
        return setProperty( "http://ns.adobe.com/pdf/1.3/", "pdf", name, value );
    }

    public void setProducer( String value )
    {
        setPdfProperty( "Producer", value );
    }

    protected Element setProperty( String namespace, String prefix,
            String name, String value )
    {
        Element propertiesRoot = getOrCreatePropertiesRoot();
        NodeList existingChildren = propertiesRoot.getChildNodes();
        for ( int i = 0; i < existingChildren.getLength(); i++ )
        {
            Node child = existingChildren.item( i );
            if ( child.getNodeType() == Node.ELEMENT_NODE )
            {
                Element childElement = (Element) child;
                if ( WordToFoUtils.isNotEmpty( childElement.getNamespaceURI() )
                        && WordToFoUtils.isNotEmpty( childElement
                                .getLocalName() )
                        && namespace.equals( childElement.getNamespaceURI() )
                        && name.equals( childElement.getLocalName() ) )
                {
                    propertiesRoot.removeChild( childElement );
                    break;
                }
            }
        }

        if ( WordToFoUtils.isNotEmpty( value ) )
        {
            Element property = document.createElementNS( namespace, prefix
                    + ":" + name );
            property.appendChild( document.createTextNode( value ) );
            propertiesRoot.appendChild( property );
            return property;
        }

        return null;
    }

    public void setSubject( String value )
    {
        setDublinCoreProperty( "title", value );
    }

    public void setTitle( String value )
    {
        setDublinCoreProperty( "title", value );
    }

    public Element setXmpProperty( String name, String value )
    {
        return setProperty( "http://ns.adobe.com/xap/1.0/", "xmp", name, value );
    }

}
