/* AbiSource
 *
 * Copyright (C) 2002 Dom Lachowicz <cinamod@hotmail.com>
 * Copyright (C) 2004 Robert Staudinger <robsta@stereolyzer.net>
 * Copyright (C) 2005 Daniel d'Andrada T. de Carvalho
 * <daniel.carvalho@indt.org.br>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */


// Internal includes
#include "OpenDocument_ContentStream_Listener.h"

// abi includes
#include <ie_impGraphic.h>
#include <fg_GraphicRaster.h>

// External includes
#include <gsf/gsf-infile.h>
#include <gsf/gsf-input-stdio.h>





/**
 * Constructor
 */
OpenDocument_ContentStream_Listener::OpenDocument_ContentStream_Listener (
                IE_Imp_OpenDocument* pImporter,
                OpenDocument_StylesStream_Listener* pSSListener)
                : OpenDocument_Stream_Listener ( pImporter ),
                    m_bAcceptingText(false),
                    m_bInSection(false),
                    m_bInTOC(false),
                    m_pSSListener(pSSListener),
                    m_imgCnt(0),
                    m_row(0),
                    m_col(0),
                    m_cel(0)
{
}


/**
 * 
 */
void OpenDocument_ContentStream_Listener::startElement (const XML_Char* pName,
                                                        const XML_Char** ppAtts)
{
    // if we're inside a TOC, ignore the contents since SXW/ODT include a copy of the TOC
    if (m_bInTOC)
        return;

    if (!UT_strcmp(pName, "office:body")) {

    } else if (!UT_strcmp(pName, "text:section" )) {

        const XML_Char * oo_sty = UT_getAttribute ("text:style-name", ppAtts);
        const XML_Char * abi_sty = _mapStyle(oo_sty);

        _insureInSection(abi_sty);

    } else if (!UT_strcmp(pName, "text:p" ) || !UT_strcmp(pName, "text:h" )) {

        UT_UTF8String oo_sty;
        
        const OO_Style* abi_sty =
            _mapStyleObj (UT_getAttribute ("text:style-name", ppAtts), oo_sty);

        if (abi_sty) {

            // append an empty paragraph with this one char
            if (abi_sty->getColBreakBefore () || abi_sty->getPageBreakBefore ()) {
                _insureInBlock(NULL);
                UT_UCSChar ucs = (abi_sty->getColBreakBefore () ? UCS_VTAB : UCS_FF);
                getDocument()->appendSpan (&ucs, 1);
            }

            const XML_Char * props[3];
            props[0] = "props";
            props[1] = abi_sty->getAbiStyle ();
            props[2] = 0;

            _insureInBlock((const XML_Char **)props);

        } else if (oo_sty.size()) {

            const XML_Char * props[3];
            props[0] = "style";
            props[1] = oo_sty.utf8_str();
            props[2] = 0;

            _insureInBlock((const XML_Char **)props);
            
        } else {
            _insureInBlock(NULL);
        }

        m_bAcceptingText = true;

    } else if (!UT_strcmp(pName, "text:span")) {
        _flush ();

        const XML_Char * oo_sty = UT_getAttribute ("text:style-name", ppAtts);
        const XML_Char * abi_sty = _mapStyle(oo_sty);

        const XML_Char * props[3];
        props[0] = "props";
        props[1] = abi_sty;
        props[2] = 0;

        _pushInlineFmt(props);
        getDocument()->appendFmt(&m_vecInlineFmt);
        
    } else if (!UT_strcmp(pName, "text:line-break")) {
        
        m_charData += UCS_LF;
        _flush ();
        
    } else if (!UT_strcmp(pName, "text:ordered-list") || !UT_strcmp(pName, "text:unordered-list")) {
        
        const XML_Char * list_atts[15];
        UT_uint32 iOffset = 0;

        // list type
        list_atts[iOffset++] = "type";
        if (!UT_strcmp(pName, "text:ordered-list")) {
            list_atts[iOffset++] = "Numbered List";
        } else {
            list_atts[iOffset++] = "Bullet List";
        }

        list_atts[iOffset++] = "id";
        list_atts[iOffset++] = "0";

        // NULL
        list_atts[iOffset++] = 0;

        getDocument()->appendList(list_atts);

        UT_DEBUGMSG(("DOM: appended a list\n"));
        
    } else if (!UT_strcmp(pName, "style:style")) {
        
        m_curStyleName.clear ();
        m_curStyleName = UT_getAttribute ("style:name", ppAtts);
        
    } else if (/* SXW */
        !UT_strcmp(pName, "style:properties") ||
        !UT_strcmp(pName, "style:columns") ||
        /* ODT */
        !UT_strcmp(pName, "style:text-properties")) {
            
        _defineSimpleStyle (ppAtts);
        
    } else if (!UT_strcmp(pName, "text:a")) {
        
        const XML_Char * xlink_atts[3];
        xlink_atts[0] = "xlink:href";
        xlink_atts[1] = UT_getAttribute("xlink:href", ppAtts);
        xlink_atts[2] = 0;
        getDocument()->appendObject(PTO_Hyperlink, xlink_atts);
        
    } else if (!UT_strcmp(pName, "text:bookmark")) {
        
        _flush ();
        const XML_Char * pName = UT_getAttribute ("text:name", ppAtts);
        _insertBookmark (pName, "start");
        _insertBookmark (pName, "end");
        
    } else if (!UT_strcmp(pName, "text:table-of-content")) {
        
        _flush ();
        _insureInBlock(NULL);

        getDocument()->appendStrux(PTX_SectionTOC, NULL);
        getDocument()->appendStrux(PTX_EndTOC, NULL);

        m_bInTOC = true;
        
    } else if (!UT_strcmp(pName, "draw:image")) {
        
        _flush ();
        _insureInBlock(NULL);
        _insertImage (ppAtts);
        
    } else if (!UT_strcmp(pName, "table:table")) {
        
        _insureInSection(NULL);
        _openTable (ppAtts);
        
    } else if (!UT_strcmp(pName, "table:table-column")) {
        
        _openColumn (ppAtts);
        
    } else if (!UT_strcmp(pName, "table:table-row")) {
        
        _openRow (ppAtts);
        
    } else if (!UT_strcmp(pName, "table:table-cell")) {
        
        _openCell (ppAtts);
        
    } else if (!UT_strcmp(pName, "text:date") ||
            !UT_strcmp(pName, "text:time") ||
            !UT_strcmp(pName, "text:page-number") ||
            !UT_strcmp(pName, "text:page-count") ||
            !UT_strcmp(pName, "text:file-name") ||
            !UT_strcmp(pName, "text:paragraph-count") ||
            !UT_strcmp(pName, "text:word-count") ||
            !UT_strcmp(pName, "text:character-count") ||
            !UT_strcmp(pName, "text:initial-creator") ||
            !UT_strcmp(pName, "text:author-name") ||
            !UT_strcmp(pName, "text:description") ||
            !UT_strcmp(pName, "text:keywords") ||
            !UT_strcmp(pName, "text:subject") ||
            !UT_strcmp(pName, "text:title")) {
                
        _flush ();

        const XML_Char * type = "";
        if(!UT_strcmp(pName, "text:date"))
            type = "date_ddmmyy";
        else if(!UT_strcmp(pName, "text:time"))
            type = "time";
        else if(!UT_strcmp(pName, "text:page-number"))
            type = "page_number";
        else if(!UT_strcmp(pName, "text:page-count"))
            type = "page_count";
        else if(!UT_strcmp(pName, "text:file-name"))
            type = "file_name";
        else if(!UT_strcmp(pName, "text:paragraph-count"))
            type = "para_count";
        else if(!UT_strcmp(pName, "text:word-count"))
            type = "word_count";
        else if(!UT_strcmp(pName, "text:character-count"))
            type = "char_count";
        else if(!UT_strcmp(pName, "text:initial-creator") || !UT_strcmp(pName, "text:author-name"))
            type = "meta_creator";
        else if(!UT_strcmp(pName, "text:description"))
            type = "meta_description";
        else if(!UT_strcmp(pName, "text:keywords"))
            type = "meta_keywords";
        else if(!UT_strcmp(pName, "text:subject"))
            type = "meta_subject";
        else if(!UT_strcmp(pName, "text:title"))
            type = "meta_title";

        const XML_Char *field_fmt[3];
        field_fmt[0] = "type";
        field_fmt[1] = type;
        field_fmt[2] = 0;
        getDocument()->appendObject(PTO_Field, (const XML_Char**)field_fmt);
        m_bAcceptingText = false;
    }
}

















/**
 *
 */
void OpenDocument_ContentStream_Listener::endElement (const XML_Char* pName)
{
    if (!UT_strcmp(pName, "text:section" )) {
        
        m_bInSection = false;
        
    } else if (!UT_strcmp(pName, "text:p" ) || !UT_strcmp(pName, "text:h" )) {
        
        _flush ();
        m_bAcceptingText = false;
        
    } else if (!UT_strcmp(pName, "text:span")) {
        
        _flush ();
        _popInlineFmt();
        getDocument()->appendFmt(&m_vecInlineFmt);
        
    } else if (!UT_strcmp(pName, "text:ordered-list") || !UT_strcmp(pName, "text:unordered-list")) {
        
    } else if (!UT_strcmp(pName, "text:a")) {
        
        _flush ();
        getDocument()->appendObject(PTO_Hyperlink, NULL);
        
    } else if (!UT_strcmp(pName, "text:table-of-content")) {
        
        m_bInTOC = false;
        
    } else if (!UT_strcmp(pName, "table:table")) {
        
        _closeTable ();
        
    } else if (!UT_strcmp(pName, "table:table-column")) {
        
        _closeColumn ();
        
    } else if (!UT_strcmp(pName, "table:table-row")) {
        
        _closeRow ();
        
    } else if (!UT_strcmp(pName, "table:table-cell")) {
        
        _closeCell ();
        
    } else if (!UT_strcmp(pName, "text:date") ||
        !UT_strcmp(pName, "text:time") ||
        !UT_strcmp(pName, "text:page-number") ||
        !UT_strcmp(pName, "text:page-count") ||
        !UT_strcmp(pName, "text:file-name") ||
        !UT_strcmp(pName, "text:paragraph-count") ||
        !UT_strcmp(pName, "text:word-count") ||
        !UT_strcmp(pName, "text:character-count") ||
        !UT_strcmp(pName, "text:initial-creator") ||
        !UT_strcmp(pName, "text:author-name") ||
        !UT_strcmp(pName, "text:description") ||
        !UT_strcmp(pName, "text:keywords") ||
        !UT_strcmp(pName, "text:subject") ||
        !UT_strcmp(pName, "text:title")) {
            
        m_bAcceptingText = true;
    }
}






/**
 * 
 */
void OpenDocument_ContentStream_Listener::charData (
                            const XML_Char* pBuffer, int length)
{
    if (pBuffer && length && m_bAcceptingText && !m_bInTOC) {
        m_charData += UT_UCS4String (pBuffer, length, true);
    }
}















/**
 * 
 */
void OpenDocument_ContentStream_Listener::_insertImage (const XML_Char** ppAtts)
{
    UT_Error error      = UT_OK;
    const XML_Char * width  = UT_getAttribute ("svg:width", ppAtts);
    const XML_Char * height = UT_getAttribute ("svg:height", ppAtts);
    const XML_Char * href   = UT_getAttribute ("xlink:href", ppAtts);

    m_imgCnt++;

    UT_ByteBuf img_buf;

    GsfInfile * pictures_dir = GSF_INFILE(gsf_infile_child_by_name(getImporter()->getOO(), "Pictures"));


    // 9 == strlen("Pictures/");
    error = _loadStream(pictures_dir, href+9, img_buf);
    g_object_unref (G_OBJECT (pictures_dir));


    if (error != UT_OK) {
        return;
    }

    const char * mimetype   = UT_strdup ("image/png");
    IE_ImpGraphic * importer    = 0;
    FG_Graphic* pFG     = 0;
    UT_ByteBuf * pictData       = 0;

    UT_String propBuffer;
    UT_String propsName;

    error = IE_ImpGraphic::constructImporter (&img_buf, IEGFT_Unknown, &importer);

    if ((error != UT_OK) || !importer) {
        FREEP(mimetype);
        goto Cleanup;
    }

    error = importer->importGraphic(&img_buf, &pFG);
    if ((error != UT_OK) || !pFG) {
        // pictData is already freed in ~FG_Graphic
        FREEP(mimetype);
        goto Cleanup;
    }

    // TODO: can we get back a vector graphic?
    pictData = static_cast<FG_GraphicRaster *>(pFG)->getRaster_PNG();

    if (!pictData) {
        // i don't think that this could ever happen, but...
        FREEP(mimetype);
        error = UT_ERROR;
        goto Cleanup;
    }

    //
    // This next bit of code will set up our properties based on the image attributes
    //

    UT_String_sprintf(propBuffer, "width:%s; height:%s", width, height);
    UT_String_sprintf(propsName, "image%d", m_imgCnt);

    const XML_Char* propsArray[5];
    propsArray[0] = (XML_Char *)"props";
    propsArray[1] = (XML_Char *)propBuffer.c_str();
    propsArray[2] = (XML_Char *)"dataid";
    propsArray[3] = (XML_Char *)propsName.c_str();
    propsArray[4] = 0;

    if (!getDocument()->appendObject (PTO_Image, propsArray)) {
        FREEP(mimetype);
        goto Cleanup;
    }

    if (!getDocument()->createDataItem(propsName.c_str(), false,
    pictData, (void*)mimetype, NULL)) {
        goto Cleanup;
    }

    Cleanup:
    DELETEP(importer);
}










/**
 * 
 */
void OpenDocument_ContentStream_Listener::_insureInBlock(const XML_Char** ppAtts)
{
    if (m_bAcceptingText) {
        return;
    }

    _insureInSection(NULL);

    if (!m_bAcceptingText) {
        getDocument()->appendStrux(PTX_Block, (const XML_Char**)ppAtts);    
        m_bAcceptingText = true;
    }
}











/**
 * 
 */
void OpenDocument_ContentStream_Listener::_insureInSection(const XML_Char* pProps) 
{
    if (m_bInSection) {
        return;
    }

    UT_String allProps(pProps);
    allProps += m_pSSListener->getSectionProps();

    const XML_Char* pAtts[3];
    pAtts[0] = "props";
    pAtts[1] = allProps.c_str();
    pAtts[2] = 0;
    getDocument()->appendStrux(PTX_Section, (const XML_Char**)pAtts);

    m_bInSection = true;
    m_bAcceptingText = false;
}






/**
 * 
 */
void OpenDocument_ContentStream_Listener::_insertBookmark (const XML_Char* pName,
                                                           const XML_Char* pType)
{
    const XML_Char* pPropsArray[5];
    pPropsArray[0] = (XML_Char *)"name";
    pPropsArray[1] = pName;
    pPropsArray[2] = (XML_Char *)"type";
    pPropsArray[3] = pType;
    pPropsArray[4] = 0;
    getDocument()->appendObject (PTO_Bookmark, pPropsArray);
}







/**
 * 
 */
void OpenDocument_ContentStream_Listener::_flush ()
{
    if (m_charData.size () > 0) {
        getDocument()->appendSpan (m_charData.ucs4_str(), m_charData.size ());
        m_charData.clear ();
    } 
}





/**
 * 
 */
void OpenDocument_ContentStream_Listener::_openTable (const XML_Char **ppProps)
{
    getDocument()->appendStrux(PTX_SectionTable, NULL);
}






/**
 * 
 */
void OpenDocument_ContentStream_Listener::_openColumn (const XML_Char** ppProps)
{
    m_col++;
}





/**
 * 
 */
void OpenDocument_ContentStream_Listener::_openRow (const XML_Char** ppProps)
{
    m_row++;
    m_cel = 0;
}





/**
 * 
 */
void OpenDocument_ContentStream_Listener::_openCell (const XML_Char** ppProps)
{
    UT_String attach;

    attach = UT_String_sprintf(
        "left-attach: %d; top-attach: %d; right-attach: %d; bot-attach: %d",
        m_cel, m_row-1, m_cel+1, m_row);

    m_cel++;

    const XML_Char *cell_props[3];
    cell_props[0] = "props";
    cell_props[1] = attach.c_str();
    cell_props[2] = 0;
    
    getDocument()->appendStrux(PTX_SectionCell,cell_props);
}







/**
 * 
 */
void OpenDocument_ContentStream_Listener::_closeTable ()
{
    getDocument()->appendStrux(PTX_EndTable, NULL);

    m_row = m_cel = m_col = 0;
}





/**
 * 
 */
void OpenDocument_ContentStream_Listener::_closeColumn ()
{
    m_col--;
}



/**
 * 
 */
void OpenDocument_ContentStream_Listener::_closeRow ()
{
    m_col--;
}




/**
 * 
 */
void OpenDocument_ContentStream_Listener::_closeCell ()
{
    getDocument()->appendStrux(PTX_EndCell,NULL);
}
  
  
/**
 * 
 */  
void OpenDocument_ContentStream_Listener::_defineSimpleStyle (const XML_Char **props)
{
    getImporter ()->defineSimpleStyle (m_curStyleName, props);
}


/**
 * 
 */
const XML_Char* OpenDocument_ContentStream_Listener::_mapStyle (const XML_Char * name) const
{
    UT_UTF8String styleName = m_pSSListener->getStyleName(name);
    return getImporter ()->mapStyle(styleName.utf8_str());
}


/**
 * 
 */
const OO_Style * OpenDocument_ContentStream_Listener::_mapStyleObj (
                        const XML_Char* name, UT_UTF8String& oo_sty) const
{
    oo_sty = m_pSSListener->getStyleName(name);
    return getImporter()->mapStyleObj (oo_sty.utf8_str());
}



/**
 * 
 */
bool OpenDocument_ContentStream_Listener::_pushInlineFmt(const XML_Char ** atts)
{
    UT_uint32 start = m_vecInlineFmt.getItemCount()+1;
    UT_uint32 k;
    XML_Char* p;
    
    for (k=0; (atts[k]); k++)
    {
        if (!UT_XML_cloneString(p,atts[k])) {
            return false;
        }
        
        if (m_vecInlineFmt.addItem(p)!=0) {
            return false;
        }
        
        if (!m_stackFmtStartIndex.push((void*)start)) {
            return false;
        }
    }
        
    return true;
}
  


/**
 * 
 */
void OpenDocument_ContentStream_Listener::_popInlineFmt(void)
{
    UT_uint32 start;
    
    if (!m_stackFmtStartIndex.pop((void **)&start))
        return;
        
    UT_uint32 k;
    UT_uint32 end = m_vecInlineFmt.getItemCount();
    const XML_Char* p;
    
    for (k=end; k>=start; k--) {
        
        p = (const XML_Char *)m_vecInlineFmt.getNthItem(k-1);
        m_vecInlineFmt.deleteNthItem(k-1);
        
        if (p)
            free((void *)p);
    }
}







/**
 * 
 */
UT_Error OpenDocument_ContentStream_Listener::_loadStream ( GsfInfile * oo,
                 const char * stream,
                 UT_ByteBuf & buf )
{
    guint8 const *data = NULL;
    size_t len = 0;
    static const size_t BUF_SZ = 4096;
  
    buf.truncate (0);
    GsfInput * input = gsf_infile_child_by_name(oo, stream);

    if (!input)
        return UT_ERROR;
  
    if (gsf_input_size (input) > 0) {
        while ((len = gsf_input_remaining (input)) > 0) {
            len = UT_MIN (len, BUF_SZ);
            if (NULL == (data = gsf_input_read (input, len, NULL))) {
                g_object_unref (G_OBJECT (input));
                return UT_ERROR;
            }
            buf.append ((const UT_Byte *)data, len);
        }
    }
  
    g_object_unref (G_OBJECT (input));
    return UT_OK;
}
