/************************************************************************
 *
 *  DrawConverter.java
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *  Copyright: 2002-2007 by Henrik Just
 *
 *  All Rights Reserved.
 * 
 *  Version 0.5 (2007-04-19)
 *
 */
 
package writer2latex.latex;

import java.util.LinkedList;
import java.util.Stack;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import writer2latex.xmerge.EmbeddedObject;
import writer2latex.xmerge.EmbeddedXMLObject;

import writer2latex.latex.util.BeforeAfter;
import writer2latex.latex.util.Context;
import writer2latex.office.ImageLoader;
import writer2latex.office.MIMETypes;
import writer2latex.office.OfficeReader;
import writer2latex.office.XMLString;
import writer2latex.util.Config;
import writer2latex.util.CSVList;
import writer2latex.util.Misc;
import writer2latex.xmerge.BinaryGraphicsDocument;

/**
 *  <p>This class handles draw elements.</p>
 */
public class DrawConverter extends ConverterHelper {

    private boolean bNeedGraphicx = false;

    // Keep track of floating frames (images, textboxes...)
    private Stack floatingFramesStack = new Stack();
	
    private Element getFrame(Element onode) {
        if (ofr.isOpenDocument()) return (Element) onode.getParentNode();
        else return onode;
    }
	
    public DrawConverter(OfficeReader ofr, Config config, ConverterPalette palette) {
        super(ofr,config,palette);
        floatingFramesStack.push(new LinkedList());
    }

    public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) {
        if (bNeedGraphicx) { 
            pack.append("\\usepackage");
            if (config.getBackend()==Config.PDFTEX) pack.append("[pdftex]");
            else if (config.getBackend()==Config.DVIPS) pack.append("[dvips]");
            pack.append("{graphicx}").nl();
        }
    }
	
    public void handleCaption(Element node, LaTeXDocumentPortion ldp, Context oc) {
        // Floating frames should be positioned *above* the label, hence
        // we use a separate ldp for the paragraphs and at this later
        LaTeXDocumentPortion capLdp = new LaTeXDocumentPortion(true);

        // Convert the caption
        if (oc.isInFigureFloat()) { // float
            capLdp.append("\\caption");
            palette.getCaptionCv().handleCaptionBody(node,capLdp,oc,false);
        }
        else { // nonfloat
            capLdp.append("\\captionof{figure}");
            palette.getCaptionCv().handleCaptionBody(node,capLdp,oc,true);
        }
		
        flushFloatingFrames(ldp,oc);
        ldp.append(capLdp);
    }
	
    public void handleDrawElement(Element node, LaTeXDocumentPortion ldp, Context oc) {
        // node must be an elment in the draw namespace
        String sName = node.getTagName();
        if (sName.equals(XMLString.DRAW_OBJECT)) {
            handleDrawObject(node,ldp,oc);
        }		
        else if (sName.equals(XMLString.DRAW_OBJECT_OLE)) {
            handleDrawObject(node,ldp,oc);
        }		
        else if ((!oc.isInHeaderFooter()) && sName.equals(XMLString.DRAW_IMAGE)) {
            handleDrawImage(node,ldp,oc);
        }		
        else if ((!oc.isInHeaderFooter()) && sName.equals(XMLString.DRAW_TEXT_BOX)) {
            handleDrawTextBox(node,ldp,oc);
        }		
        else if (sName.equals(XMLString.DRAW_A)) {
            // we handle this like text:a
            palette.getFieldCv().handleAnchor(node,ldp,oc);
        }
        else if (sName.equals(XMLString.DRAW_FRAME)) {
            // OpenDocument: Get the actual draw element in the frame
            handleDrawElement(Misc.getFirstChildElement(node),ldp,oc);
        }
        else {
            // Other drawing objects (eg. shapes) are currently not supported
            ldp.append("[Warning: Draw object ignored]");
        }
    }
	
    //-----------------------------------------------------------------
    // handle draw:object elements (OOo objects such as Chart, Math,...)
		
    private void handleDrawObject(Element node, LaTeXDocumentPortion ldp, Context oc) {
        String sHref = Misc.getAttribute(node,XMLString.XLINK_HREF);
		
        if (sHref!=null) { // Embedded object in package or linked object
            if (ofr.isInPackage(sHref)) { // Embedded object in package
                if (sHref.startsWith("#")) { sHref=sHref.substring(1); }
                if (sHref.startsWith("./")) { sHref=sHref.substring(2); }
                EmbeddedObject object = palette.getEmbeddedObject(sHref); 
                if (object!=null) {
                    if (MIMETypes.MATH.equals(object.getType()) || MIMETypes.ODF.equals(object.getType())) { // Formula!
                        try {
                            Document settings = ((EmbeddedXMLObject) object).getSettingsDOM();
                            Document formuladoc = ((EmbeddedXMLObject) object).getContentDOM();
                            Element formula = Misc.getChildByTagName(formuladoc,XMLString.MATH_MATH);
                            ldp.append(" $")
                               .append(palette.getMathmlCv().convert(settings,formula))
                               .append("$");
                            if (Character.isLetterOrDigit(getNextChar(node))) { ldp.append(" "); }
                        }
                        catch (org.xml.sax.SAXException e) {
                            e.printStackTrace();
                        }
                        catch (java.io.IOException e) {
                            e.printStackTrace();
                        }
	                }
                    else { // unsupported object
                        boolean bIgnore = true;
                        if (ofr.isOpenDocument()) { // look for replacement image
                            Element replacementImage = Misc.getChildByTagName(getFrame(node),XMLString.DRAW_IMAGE);
                            if (replacementImage!=null) {
                                handleDrawImage(replacementImage,ldp,oc);
                                bIgnore = false;
                            }
                        }
                        if (bIgnore) { 
                            ldp.append("[Warning: object ignored]");
                        }
                    }

                }
            }
        }
        else { // flat xml, object is contained in node
            Element formula = Misc.getChildByTagName(node,XMLString.MATH_MATH);
            if (formula!=null) {
                ldp.append(" $")
                   .append(palette.getMathmlCv().convert(null,formula))
                   .append("$");
                if (Character.isLetterOrDigit(getNextChar(node))) { ldp.append(" "); }
            }
            else { // unsupported object
                boolean bIgnore = true;
                if (ofr.isOpenDocument()) { // look for replacement image
                    Element replacementImage = Misc.getChildByTagName(getFrame(node),XMLString.DRAW_IMAGE);
                    if (replacementImage!=null) {
                        handleDrawImage(replacementImage,ldp,oc);
                        bIgnore = false;
                    }
                }
                if (bIgnore) { 
                    ldp.append("[Warning: object ignored]");
                }
            }

        }

    }
	
    //--------------------------------------------------------------------------
    // Create float environment
    private void applyFigureFloat(BeforeAfter ba, Context oc) {
        // todo: check context...
        if (config.floatFigures() && !oc.isInFrame() && !oc.isInTable()) {
           if (oc.isInMulticols()) {
               ba.add("\\begin{figure*}","\\end{figure*}\n");
           }
           else {
               ba.add("\\begin{figure}","\\end{figure}\n");
           }
           if (config.getFloatOptions().length()>0) {
               ba.add("["+config.getFloatOptions()+"]","");
           }
           ba.add("\n","");
           oc.setInFigureFloat(true);
        }
        if (!oc.isInFrame() && config.alignFrames()) {
            // Avoid nesting center environment
            ba.add("\\begin{center}\n","\n\\end{center}\n");
        }
	
    }
	
    //--------------------------------------------------------------------------
    // Handle draw:image elements
	
    private void handleDrawImage(Element node, LaTeXDocumentPortion ldp, Context oc) {
        // Include graphics if allowed by the configuration
        switch (config.imageContent()) {
        case Config.IGNORE:
            // Ignore graphics silently
            return;
        case Config.WARNING:
            System.err.println("Warning: Images are not allowed");
            return;
        case Config.ERROR:
            ldp.append("% Error in document: An image was ignored");
            return;
        }

        Element frame = getFrame(node);
        String sName = frame.getAttribute(XMLString.DRAW_NAME);
        palette.getFieldCv().addTarget(frame,"|graphics",ldp);
        String sAnchor = frame.getAttribute(XMLString.TEXT_ANCHOR_TYPE);
        //if (oc.isInFrame() || "as-char".equals(sAnchor)) {
        if ("as-char".equals(sAnchor)) {
            handleDrawImageAsChar(node,ldp,oc);
        }
        else {
            ((LinkedList) floatingFramesStack.peek()).add(node);
        }
    }
	
    private void handleDrawImageAsChar(Element node, LaTeXDocumentPortion ldp, Context oc) {
        ldp.append(" ");
        includeGraphics(node,ldp,oc);
        ldp.append(" ");
    }

    private void handleDrawImageFloat(Element node, LaTeXDocumentPortion ldp, Context oc) {
        Context ic = (Context) oc.clone();
        BeforeAfter ba = new BeforeAfter();

        applyFigureFloat(ba,ic);
		
        ldp.append(ba.getBefore());
        includeGraphics(node,ldp,ic);
        ldp.append(ba.getAfter());
    }

    private void includeGraphics(Element node, LaTeXDocumentPortion ldp, Context oc) {
        String sFileName = null;
        boolean bCommentOut = true;
        String sHref = node.getAttribute(XMLString.XLINK_HREF);
		
        if (node.hasAttribute(XMLString.XLINK_HREF) && !ofr.isInPackage(sHref)) {
            // Linked image is not yet handled by ImageLoader. This is a temp.
            // solution (will go away when ImageLoader is finished)
            sFileName = sHref;
            // In OpenDocument package format ../ means "leave the package"
            if (ofr.isOpenDocument() && ofr.isPackageFormat() && sFileName.startsWith("../")) {
                sFileName=sFileName.substring(3);
            }
            int nExtStart = sHref.lastIndexOf(".");
            String sExt = nExtStart>=0 ? sHref.substring(nExtStart).toLowerCase() : "";
            // Accept only relative filenames and supported filetypes:
            bCommentOut = sFileName.indexOf(":")>-1 || !(
                config.getBackend()==config.UNSPECIFIED ||
                (config.getBackend()==config.PDFTEX && MIMETypes.JPEG_EXT.equals(sExt)) ||
                (config.getBackend()==config.PDFTEX && MIMETypes.PNG_EXT.equals(sExt)) ||
                (config.getBackend()==config.DVIPS && MIMETypes.EPS_EXT.equals(sExt)));
        }
        else { // embedded or base64 encoded image
            BinaryGraphicsDocument bgd = palette.getImageLoader().getImage(node);
            if (bgd!=null) {
                palette.addDocument(bgd);
                sFileName = bgd.getFileName();
                String sMIME = bgd.getDocumentMIMEType();
                bCommentOut = !(
                    config.getBackend()==config.UNSPECIFIED ||
                    (config.getBackend()==config.PDFTEX && MIMETypes.JPEG.equals(sMIME)) ||
                    (config.getBackend()==config.PDFTEX && MIMETypes.PNG.equals(sMIME)) ||
                    (config.getBackend()==config.DVIPS && MIMETypes.EPS.equals(sMIME)));
            }
        }
		
        if (sFileName==null) {
            ldp.append("[Warning: Image not found]");
            return;
        }
		
        // Now for the actual inclusion:
        bNeedGraphicx = true;
        /* TODO (0.4): handle cropping and mirror:
           style:mirror can be none, vertical (lodret), horizontal (vandret),
           horizontal-on-odd, or
           horizontal-on-even (vandret på ulige hhv. lige side).
   		   mirror is handled with scalebox, eg:
        		%\\scalebox{-1}[1]{...}
		   can check for even/odd page first!!
	
          fo:clip="rect(t,r,b,l) svarer til trim
          value can be auto - no clip!
		  cropping is handled with clip and trim:
		  \\includegraphics[clip,trim=l b r t]{...}
		  note the different order from xsl-fo!
         */

        if (bCommentOut) {
            ldp.append(" [Warning: Image ignored] ");
            ldp.append("% Unhandled or unsupported graphics:").nl().append("%");
        }
        ldp.append("\\includegraphics");

        CSVList options = new CSVList(',');
        if (!config.keepImageSize()) {
            Element frame = getFrame(node);
            String sWidth = Misc.truncateLength(frame.getAttribute(XMLString.SVG_WIDTH));
            String sHeight = Misc.truncateLength(frame.getAttribute(XMLString.SVG_HEIGHT));
            if (sWidth!=null) { options.addValue("width="+sWidth); }
            if (sHeight!=null) { options.addValue("height="+sHeight); }
        }
        if (config.getImageOptions().length()>0) {
            options.addValue(config.getImageOptions()); // TODO: New CSVList...
        }
        if (!options.isEmpty()) {
            ldp.append("[").append(options.toString()).append("]");
        }

        if (config.removeGraphicsExtension()) {
            sFileName = Misc.removeExtension(sFileName);
        }
        ldp.append("{").append(sFileName).append("}");
        if (bCommentOut) { ldp.nl(); }
    }

    //--------------------------------------------------------------------------
    // handle draw:text-box element
	
    private void handleDrawTextBox(Element node, LaTeXDocumentPortion ldp, Context oc) {
        Element frame = getFrame(node);
        String sName = frame.getAttribute(XMLString.DRAW_NAME);
        palette.getFieldCv().addTarget(frame,"|frame",ldp);
        String sAnchor = frame.getAttribute(XMLString.TEXT_ANCHOR_TYPE);
        //if (oc.isInFrame() || "as-char".equals(sAnchor)) {
        if ("as-char".equals(sAnchor)) {
            makeDrawTextBox(node, ldp, oc);
        }
        else {
            ((LinkedList) floatingFramesStack.peek()).add(node);
        }
    }
	
    private void handleDrawTextBoxFloat(Element node, LaTeXDocumentPortion ldp, Context oc) {
        BeforeAfter ba = new BeforeAfter();
        Context ic = (Context) oc.clone();

        applyFigureFloat(ba,ic);
		
        ldp.append(ba.getBefore());
        makeDrawTextBox(node, ldp, ic);
        ldp.append(ba.getAfter());
    }

    private void makeDrawTextBox(Element node, LaTeXDocumentPortion ldp, Context oc) {
        Context ic = (Context) oc.clone();
        ic.setInFrame(true);
        ic.setNoFootnotes(true);
		
        // Check to see, if this is really a container for a figure caption
        boolean bIsCaption = false;
        if (ofr.isSingleParagraph(node)) {
            Element par = Misc.getFirstChildElement(node);
            String sSeqName = ofr.getSequenceName(par);
            if (ofr.isFigureSequenceName(sSeqName)) { bIsCaption = true; }
        }

        String sWidth = Misc.truncateLength(getFrame(node).getAttribute(XMLString.SVG_WIDTH));
        if (!bIsCaption) {
            ldp.append("\\begin{minipage}{").append(sWidth).append("}").nl();
        }
        floatingFramesStack.push(new LinkedList());
        palette.getBlockCv().traverseBlockText(node,ldp,ic);
        flushFloatingFrames(ldp,ic);
        floatingFramesStack.pop();
        if (!bIsCaption) {
            ldp.append("\\end{minipage}");
        }
        if (!oc.isNoFootnotes()) { palette.getNoteCv().flushFootnotes(ldp,oc); }

    }

    //-------------------------------------------------------------------------
    //handle any pending floating frames
    
    public void flushFloatingFrames(LaTeXDocumentPortion ldp, Context oc) {
	    // todo: fix language
        LinkedList floatingFrames = (LinkedList) floatingFramesStack.peek();
        int n = floatingFrames.size();
        if (n==0) { return; }
        for (int i=0; i<n; i++) {
            Element node = (Element) floatingFrames.get(i);
            String sName = node.getNodeName();
            if (sName.equals(XMLString.DRAW_IMAGE)) {
                handleDrawImageFloat(node,ldp,oc);
            }
            else if (sName.equals(XMLString.DRAW_TEXT_BOX)) {
                handleDrawTextBoxFloat(node,ldp,oc);
            }
        }
        floatingFrames.clear();
    }
	
    //-------------------------------------------------------------------------
    //Some utility methods

    // Return the next node of *this paragraph* in logical order
    // (Parents before children, siblings from left to right)
    // Do not descend into draw elements and footnotes/endnotes
    private Node getNextNode(Node node) {
        // If element node: Next node is first child
        if (node.getNodeType()==Node.ELEMENT_NODE && node.hasChildNodes() &&
            !ofr.isDrawElement(node) && !ofr.isNoteElement(node)) {
            return node.getFirstChild();
        }
        // else iterate for next node, but don't leave this paragraph
        Node next = node;
        do {
            // First look for next sibling
            if (next.getNextSibling()!=null) { return next.getNextSibling(); }
            // Then move to parent, if this is the text:p node, we are done
            next = next.getParentNode();
            if (next.getNodeType()==Node.ELEMENT_NODE &&
                next.getNodeName().equals(XMLString.TEXT_P)) {
                return null;
            }
        } while (next!=null);
        return null;
    }

    // Return the next character in logical order
    private char getNextChar(Node node) {
        Node next = node;
        do {
            next = getNextNode(next);
            if (next!=null && next.getNodeType()==Node.TEXT_NODE &&
                next.getNodeValue().length()>0) {
                // Found the next character!
                return next.getNodeValue().charAt(0);
            }
        } while (next!=null);
        // No more text in this paragraph!
        return '\u0000';
    }
}