/************************************************************************
 *
 *  TableConverter.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-05-11)
 *
 */

package writer2latex.xhtml;

import java.util.Vector;

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

import writer2latex.util.Misc;
import writer2latex.util.Config;
import writer2latex.util.SimpleInputBuffer;
import writer2latex.office.XMLString;
import writer2latex.office.StyleWithProperties;
import writer2latex.office.OfficeReader;
//import writer2latex.office.TableLine;
import writer2latex.office.TableReader;

public class TableConverter extends ConverterHelper {

    // The collection of all table names
    // TODO: Navigation should be handled here rather than in Converter.java
    protected Vector<String> sheetNames = new Vector<String>();
	
    public TableConverter(OfficeReader ofr, Config config, Converter converter) {
        super(ofr,config,converter);
    }
	
    /** Converts an office node as a complete table (spreadsheet) document
     *
     *  @param onode the Office node containing the content to convert
     */
    public void convertTableContent(Element onode) {
        Element hnode = null;
        if (!onode.hasChildNodes()) { return; }
        if (!config.xhtmlCalcSplit()) { hnode = nextOutFile(); }
        NodeList nList = onode.getChildNodes();
        int nLen = nList.getLength();
        for (int i=0; i<nLen; i++) {
            Node child = nList.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                String sNodeName = child.getNodeName();
                if (sNodeName.equals(XMLString.TABLE_TABLE)) {
                    StyleWithProperties style = ofr.getTableStyle(
                        Misc.getAttribute(child,XMLString.TABLE_STYLE_NAME));
                    if (style==null || !"false".equals(style.getProperty(XMLString.TABLE_DISPLAY))) {
                        if (config.xhtmlCalcSplit()) { hnode = nextOutFile(); }
                        // Collect name
                        String sName = Misc.getAttribute(child,XMLString.TABLE_NAME);
                        sheetNames.add(sName);

                        // Add heading
                        Element heading = converter.createElement("h2");
                        hnode.appendChild(heading);
                        heading.setAttribute("id","tableheading"+(sheetNames.size()-1));
                        heading.appendChild(converter.createTextNode(sName));
    
                        // Handle the table
                        handleTable(child,hnode);
    
                        // Add frames belonging to this table
                        Element div = converter.createElement("div");
                        Element shapes = Misc.getChildByTagName(child,XMLString.TABLE_SHAPES);
                        if (shapes!=null) {
                            Node shape = shapes.getFirstChild();
                            while (shape!=null) {
                                if (OfficeReader.isDrawElement(shape)) {
                                    // Actually only the first parameter is used
                                    getDrawCv().handleDrawElement((Element)shape,div,null,DrawConverter.CENTERED);
                                }
                                shape = shape.getNextSibling();
                            }
                        }
                        getDrawCv().flushFrames(div);
                        if (div.hasChildNodes()) { hnode.appendChild(div); }
                    }
                }
            }
        }
    }
	
    private Element nextOutFile() {
        Element hnode = converter.nextOutFile();
        // Add title:
        String sTitle = converter.getMetaData().getTitle();
        if (sTitle!=null) {
            Element title = converter.createElement("h1");
            hnode.appendChild(title);
            title.appendChild(converter.createTextNode(sTitle));
        }
        return hnode;
    }
	
    /** Process a table:table tag 
     * 
     *  @param onode the Office node containing the table element 
     *  @param hnode the XHTML node to which the table should be attached
     */
    public void handleTable(Node onode, Node hnode) {
        TableReader tblr = ofr.getTableReader((Element)onode);
        // Create table
        Element table = converter.createElement("table");
        converter.addTarget(table,tblr.getTableName()+"%7Ctable");
        hnode.appendChild(table);

        // Apply table style
        // IE needs the cellspacing attribute, as it doesn't understand the css border-spacing attribute
        table.setAttribute("cellspacing","0");
        boolean bIsSubtable = tblr.isSubTable();
        applyTableStyle(tblr.getTableStyleName(),table,bIsSubtable);

        // Determine which rows and columns to include
        int nRowCount;
        int nColCount;
        if (ofr.isText()) { // take them all
            nRowCount = tblr.getRowCount();
            nColCount = tblr.getColCount();
        }
        else { // skip trailing empty rows and columns
            nRowCount = tblr.getMaxRowCount();
            nColCount = tblr.getMaxColCount();
        }
		
        // Check to see, if the first row contains any colspan
        boolean bFirstRowColSpan = false;
        for (int nCol=0; nCol<nColCount; nCol++) {
            Node cell = tblr.getCell(0,nCol);
            if (cell!=null && XMLString.TABLE_TABLE_CELL.equals(cell.getNodeName())) {
                String sColSpan = Misc.getAttribute(cell,XMLString.TABLE_NUMBER_COLUMNS_SPANNED);
                if (Misc.getPosInteger(sColSpan,1)>1) {
                    bFirstRowColSpan = true;
                }
            }
        }

        // Create columns; only for tables with relative width
        // Otherwise we set the cell width. Reason: IE and Mozilla does not
        // interpret column width the same way. IE excludes padding and border,
        // Mozilla (like OOo) includes them.
        // If the first row contains colspan we have to add <col> anyway
        if (!config.xhtmlIgnoreTableDimensions()) {
            if (tblr.getRelTableWidth()!=null) {
                for (int nCol=0; nCol<nColCount; nCol++) {
                    Element col = converter.createElement("col");
                    table.appendChild(col);
                    col.setAttribute("style","width:"+tblr.getRelColumnWidth(nCol));
                }
            }
            else if (bFirstRowColSpan) {
                for (int nCol=0; nCol<nColCount; nCol++) {
                    Element col = converter.createElement("col");
                    table.appendChild(col);
                    col.setAttribute("style","width:"+getTableSc().colScale(tblr.getColumnWidth(nCol)));
                }
            }
        }

        // Create groups
        Element thead = converter.createElement("thead");
        Element tbody = converter.createElement("tbody");

        // Create rows
        Element tgroup = thead;
        if (tblr.getRow(nRowCount-1).isHeader()) { // no body? xhtml will be unhappy
            tgroup = tbody;
        }
        for (int nRow=0; nRow<nRowCount; nRow++) {
            if (!tblr.getRow(nRow).isHeader()) {
                tgroup = tbody;
            }

            //// Is this row visible?
            //if (!"collapse".equals(tblr.getRow(nRow).getVisibility())) {
                // TODO: Maybe "filtered" rows should be hidden too?

                // Create row and apply row style
                Element tr = converter.createElement("tr");
                tgroup.appendChild(tr);
                applyRowStyle(tblr.getRow(nRow).getStyleName(),tr);

                for (int nCol=0; nCol<nColCount; nCol++) {
                    Node cell = tblr.getCell(nRow,nCol);
                    if (cell!=null && XMLString.TABLE_TABLE_CELL.equals(cell.getNodeName())) {
                        // Create cell
                        Element td = converter.createElement("td");
                        tr.appendChild(td);
                        int nRowSpan = Misc.getPosInteger(Misc.getAttribute(cell,XMLString.TABLE_NUMBER_ROWS_SPANNED),1);
                        if (nRowSpan>1) {
                            //// Reduce if we pass hidden row
                            //int nReduce = 0;
                            //for (int i=1; i<nRowSpan; i++) {
                            //    if ("collapse".equals(tblr.getRow(nRow+i).getVisibility())) { nReduce++; }
                            //}
                            //nRowSpan-=nReduce;
                            td.setAttribute("rowspan",Integer.toString(nRowSpan));
                        }
                        String sColSpan = Misc.getAttribute(cell,XMLString.TABLE_NUMBER_COLUMNS_SPANNED);
                        if (Misc.getPosInteger(sColSpan,1)>1) {
                            td.setAttribute("colspan",sColSpan);
                        }
    
                        // Handle content
                        if (!isEmptyCell(cell)) {
                            getTextCv().traverseBlockText(cell,td);
                        }
                        else {
                            Element par = converter.createElement("p");
                            td.appendChild(par);
                            par.setAttribute("style","margin:0;font-size:1px");
                            par.appendChild(converter.createTextNode("\u00A0"));
                        }

                        // Is this a subtable?
                        Node subTable = Misc.getChildByTagName(cell,XMLString.TABLE_SUB_TABLE);
                        String sTotalWidth=null;
                        if (Misc.getPosInteger(sColSpan,1)==1) {
                            sTotalWidth = tblr.getCellWidth(nRow,nCol);
                        }
                        String sValueType = ofr.isOpenDocument() ?
                            Misc.getAttribute(cell,XMLString.OFFICE_VALUE_TYPE) :
                            Misc.getAttribute(cell,XMLString.TABLE_VALUE_TYPE);
                        applyCellStyle(tblr.getCellStyleName(nRow,nCol), sTotalWidth, sValueType, td, subTable!=null);
                    }
                    else if (XMLString.TABLE_COVERED_TABLE_CELL.equals(cell.getNodeName())) {
                        // covered table cells are not part of xhtml table model
                    }
                }
            //}
        }
        // Put the content into the table
        if (thead.hasChildNodes()) { table.appendChild(thead); }
        table.appendChild(tbody);
    }
	
    private boolean isEmptyCell(Node cell) {
        if (!cell.hasChildNodes()) {
            return true;
        }
        else if (OfficeReader.isSingleParagraph(cell)) {
            Element par = Misc.getChildByTagName(cell,XMLString.TEXT_P);
            return par==null || !par.hasChildNodes();
        }
        return false;
    }
	
    private void applyTableStyle(String sStyleName, Element table, boolean bIsSubTable) {
        StyleInfo info = new StyleInfo();
        getTableSc().applyStyle(sStyleName,info);

        if (!config.xhtmlIgnoreTableDimensions()) {
            StyleWithProperties style = ofr.getTableStyle(sStyleName);
            if (style!=null) {
                // Set table width
                String sWidth = style.getProperty(XMLString.STYLE_REL_WIDTH);
                if (sWidth!=null) {
                    info.props.addValue("width",sWidth);
                }
                else {
                    sWidth = style.getProperty(XMLString.STYLE_WIDTH);
                    if (sWidth!=null) {
                        info.props.addValue("width",getTableSc().colScale(sWidth));
                    }
                }
            }
        }

        // Writer uses a separating border model, Calc a collapsing:
        // props.addValue("border-collapse", bCalc ? "collapse" : "separate");
        // For now always use separating model:
        info.props.addValue("border-collapse", "separate");
        info.props.addValue("border-spacing", "0");

        info.props.addValue("table-layout","fixed");

        //info.props.addValue("empty-cells","show"); use &nbsp; instead...

        if (ofr.isSpreadsheet()) { info.props.addValue("white-space","nowrap"); }

        if (bIsSubTable) {
            // Should try to fill the cell; hence:
            info.props.addValue("width","100%");
            info.props.addValue("margin","0");
        }
        applyStyle(info,table);
    }

    private void applyRowStyle(String sStyleName, Element row) {
        StyleInfo info = new StyleInfo();
        getRowSc().applyStyle(sStyleName,info);

        if (!config.xhtmlIgnoreTableDimensions()) {
            StyleWithProperties style = ofr.getRowStyle(sStyleName);
            if (style!=null) {
                // Translates row style properties
                // OOo offers style:row-height and style:min-row-height
                // In css row heights are always minimal, so both are exported as height
                // If neither is specified, the tallest cell rules; this fits with css.
                String s = style.getAbsoluteProperty(XMLString.STYLE_ROW_HEIGHT);
                // Do not export minimal row height; causes trouble with ie
                //if (s==null) { s = style.getAbsoluteProperty(XMLString.STYLE_MIN_ROW_HEIGHT); }
                if (s!=null) { info.props.addValue("height",getRowSc().scale(s)); }
            }
        }

        applyStyle(info,row);
    }
	
    private void applyCellStyle(String sStyleName, String sTotalWidth, String sValueType, Element cell, boolean bIsSubTable) {
        StyleInfo info = new StyleInfo();
        getCellSc().applyStyle(sStyleName,info);

        StyleWithProperties style = ofr.getCellStyle(sStyleName);
        if (style!=null) {
            if (!config.xhtmlIgnoreTableDimensions()) {
                String sEdge = "0";
    
                // Set the cell width. This is calculated as
                // "total cell width" - "border" - "padding"
                String s = style.getProperty(XMLString.FO_PADDING_LEFT);
                if (s!=null) {
                    sEdge=Misc.add(sEdge,getTableSc().colScale(s));
                }
                s = style.getProperty(XMLString.FO_PADDING_RIGHT);
                if (s!=null) { 
                    sEdge=Misc.add(sEdge,getTableSc().colScale(s));
                }
                s = style.getProperty(XMLString.FO_PADDING);
                if (s!=null) {
                    sEdge=Misc.add(sEdge,Misc.multiply("200%",getTableSc().colScale(s)));
                }
                s = style.getProperty(XMLString.FO_BORDER_LEFT);
                if (s!=null) {
                    sEdge=Misc.add(sEdge,getTableSc().colScale(borderWidth(s)));
                }
                s = style.getProperty(XMLString.FO_BORDER_RIGHT);
                if (s!=null) { 
                    sEdge=Misc.add(sEdge,getTableSc().colScale(borderWidth(s)));
                }
                s = style.getProperty(XMLString.FO_BORDER);
                if (s!=null) {
                    sEdge=Misc.add(sEdge,Misc.multiply("200%",getTableSc().colScale(borderWidth(s))));
                }

                if (sTotalWidth!=null) {
                    info.props.addValue("width",Misc.sub(getTableSc().colScale(sTotalWidth),sEdge));
                }
            }

            // Automatic horizontal alignment (calc only)
            if (ofr.isSpreadsheet() && !"fix".equals(style.getProperty(XMLString.STYLE_TEXT_ALIGN_SOURCE))) {
                // Strings go left, other types (float, time, date, percentage, currency, boolean) go right
                // The default is string
                info.props.addValue("text-align", sValueType==null || "string".equals(sValueType) ? "left" : "right");
            }
        }
		
        if (!cell.hasChildNodes()) { // hack to handle empty cells even in msie
            // info.props.addValue("line-height","1px"); TODO: Reenable this...
            cell.appendChild( converter.createTextNode("\u00A0") );
        }
		
        if (bIsSubTable) {
            // Cannot set height of a subtable, if the subtable does not fill
            // the entire cell it is placed at the top
            info.props.addValue("vertical-align","top");
            // Don't add padding if there's a subtable in the cell!
            info.props.addValue("padding","0");
        }

        applyStyle(info,cell);
    }
	
    private String borderWidth(String sBorder) {
        if (sBorder==null || sBorder.equals("none")) { return "0"; }
        SimpleInputBuffer in = new SimpleInputBuffer(sBorder);
        while (in.peekChar()!='\0') {
            // Skip spaces
            while(in.peekChar()==' ') { in.getChar(); }
            // If it's a number it must be a unit -> get it
            if ('0'<=in.peekChar() && in.peekChar()<='9') {
                return in.getNumber()+in.getIdentifier();
            }
            // skip other characters
            while (in.peekChar()!=' ' && in.peekChar()!='\0') { } 
        }
        return "0";
    }

	
}
