package com.sun.star.comp.xsltfilter;

/************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

//Standard Java classes
import java.io.FileWriter;
import java.util.zip.Inflater;
import java.util.zip.Deflater;

//StarOffice Interfaces and UNO
import com.sun.star.bridge.XBridgeFactory;
import com.sun.star.bridge.XBridge;
import com.sun.star.connection.XConnector;
import com.sun.star.connection.XConnection;
import com.sun.star.container.XNameContainer;
import com.sun.star.embed.XTransactedObject;
import com.sun.star.io.XStream;
import com.sun.star.io.XSeekable;
import com.sun.star.io.XInputStream;
import com.sun.star.io.XOutputStream;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.XComponent;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uno.UnoRuntime;

/** This class is an xalan extension class. It provider 2 elements
 *  and 2 functions to used in xslt script. With this elements and functions
 *  we can convert between oledata between Wordml and OOo flat.
 *  To use it, we need a running OOo. There are two ways to get the XMultiServiceFactory.
 *  When called by OOo xslt filter, an XMultiServiceFactory will be add to the transformer
 *  by setParameter(), then we can get it using getParameter(). Another way is using an
 *  XConnection to connect to a running OOo. We connect to a running OOo, we need know the
 *  uno url. It can be set in the xslt script. The default uno url is:
 *  "uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager"
 *  see XSLTXalanOLEExtracter.java
 */
public class XSLTFilterOLEExtracter {

    protected XMultiServiceFactory m_xMSF;
    protected XNameContainer m_Storage;
    protected XStream m_RootStream;
    protected XConnection m_Connection;
    protected String sConnectionString;
    private static final String UNO_URL = "uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager";

    public XSLTFilterOLEExtracter() {
    }

    public void init(String unoUrl) {
        if (unoUrl == null || unoUrl.equals("")) {
            unoUrl = UNO_URL;
        }
        debugln("Init with uno url=" + unoUrl);
        if (null == m_xMSF) {
            try {
                m_xMSF = connectAwareGetServiceFactory();
            } catch (Exception ex) {
                System.err.println("Could not connect to the office '" + unoUrl + "'\n" + ex.getMessage());
            }
        }
    }

    public void exit() {
        m_Storage = null;
        m_xMSF = null;
        if (null != m_Connection) {
            try {
                m_Connection.close();
            } catch (Exception ex) {
                System.err.println("Could not close connection to the office.\n" + ex.getMessage());
            }
        }
    }
    //If aName = "oledata.mso" then we load the root storage from the given base64 string
    //Otherwise we compress the stream and add it to the root storage under the name of aName
    public void insertByName(String aName, String aBase64) {
        debugln("insertByName(" + aName + " : " + aBase64 + ")");
        if (aName.equals("oledata.mso")) {
            loadRootStorageFromBase64(aBase64);
        } else {
            ensureCreateRootStorage();
            insertSubStorage(aName, aBase64);
        }
    }
    //If aName = "oledata.mso" then we return the base64 encoded string of the root storage
    //Otherwise we return the base64 encoded string of the sub stream under the name of aName
    public String getByName(String aName) {
        if (aName.equals("oledata.mso")) {
            try {
                //get the length and seek to 0
                XSeekable xSeek = (XSeekable) UnoRuntime.queryInterface(XSeekable.class, m_RootStream);
                int oleLength = (int) xSeek.getLength();
                xSeek.seek(0);
                xSeek = null;
                //read all bytes
                XInputStream xInput = m_RootStream.getInputStream();
                byte oledata[][] = new byte[1][oleLength];
                xInput.readBytes(oledata, oleLength);
                //return the base64 encoded string
                return Base64.encodeBytes(oledata[0]);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } else {
            return getEncodedSubStorage(aName);
        }
        return "";
    }
    //get the sub stream which name = aName, decompress it and return the base64 encoded string
    public String getEncodedSubStorage(String aName) {
        debugln("getByName(" + aName + ")");
        try {
            if (!m_Storage.hasByName(aName)) {
                return "Not Found:" + aName;
            }
            Object oSubStream = m_Storage.getByName(aName);
            if (oSubStream == null) {
                return "Not Found:" + aName;
            }
            XInputStream xSubStream = (XInputStream) UnoRuntime.queryInterface(XInputStream.class,
                    oSubStream);
            if (xSubStream == null) {
                return "Not Found:" + aName;
            }
            //The first four byte are the length of the uncompressed data
            byte pLength[][] = new byte[1][4];
            XSeekable xSeek = (XSeekable) UnoRuntime.queryInterface(XSeekable.class, xSubStream);
            xSeek.seek(0);
            xSeek = null;
            //Get the uncompressed length
            int readbytes = xSubStream.readBytes(pLength, 4);
            if (4 != readbytes) {
                System.out.println("readbytes:" + readbytes);
                return "Can not read the length.";
            }
            int oleLength = (pLength[0][0] << 0) + (pLength[0][1] << 8) + (pLength[0][2] << 16) + (pLength[0][3] << 24);
            byte pContents[][] = new byte[1][oleLength];
            //Read all bytes. The compressed length should less then the uncompressed length
            readbytes = xSubStream.readBytes(pContents, oleLength);
            if (oleLength < readbytes) {
                return "oleLength :" + oleLength + " readbytes: " + readbytes;
            }

            // Decompress the bytes
            Inflater decompresser = new Inflater();
            decompresser.setInput(pContents[0], 0, readbytes);
            byte[] result = new byte[oleLength];
            int resultLength = decompresser.inflate(result);
            decompresser.end();

            //return the base64 string of the uncompressed data
            return Base64.encodeBytes(result);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return "";
    }

    public XStream CreateTempFileStream(XMultiServiceFactory xMSF) {
        // try to get temporary file representation
        XStream xTempFileStream = null;
        try {
            Object oTempFile = xMSF.createInstance("com.sun.star.io.TempFile");
            xTempFileStream = (XStream) UnoRuntime.queryInterface(XStream.class, oTempFile);
        } catch (Exception e) {
        }

        if (xTempFileStream == null) {
            System.out.println("Can't create temporary file!");
        }

        return xTempFileStream;
    }
    //decode the base64 string and create an com.sun.star.embed.OLESimpleStorage from it
    public void loadRootStorageFromBase64(String aBase64) {
        try {
            //Decode and write the data to an temp stream
            byte[] oledata = Base64.decode(aBase64);
            m_RootStream = CreateTempFileStream(m_xMSF);
            XOutputStream xOutput = m_RootStream.getOutputStream();
            xOutput.writeBytes(oledata);
            xOutput.flush();
            //Get the input stream and seek to begin
            XInputStream xInput = m_RootStream.getInputStream();
            XSeekable xSeek = (XSeekable) UnoRuntime.queryInterface(XSeekable.class, xInput);
            xSeek.seek(0);
            oledata = null;
            xSeek = null;

            //create an com.sun.star.embed.OLESimpleStorage from the temp stream
            Object pArgs[] = new Object[1];
            pArgs[0] = (Object) xInput;
            Object oTempStorage = m_xMSF.createInstanceWithArguments("com.sun.star.embed.OLESimpleStorage", pArgs);
            pArgs = null;

            m_Storage = (XNameContainer) UnoRuntime.queryInterface(XNameContainer.class, oTempStorage);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //Create a empty OLESimpleStorage if there is not one
    public void ensureCreateRootStorage() {
        if (null == m_RootStream || null == m_Storage) {
            try {
                m_RootStream = CreateTempFileStream(m_xMSF);

                Object pArgs[] = new Object[1];
                pArgs[0] = (Object) m_RootStream;
                Object oTempStorage = m_xMSF.createInstanceWithArguments("com.sun.star.embed.OLESimpleStorage", pArgs);
                pArgs = null;

                m_Storage = (XNameContainer) UnoRuntime.queryInterface(XNameContainer.class, oTempStorage);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    //decode the base64 string and insert the length and the compressed data of it to
    //the root storage as a sub stream under aName
    public void insertSubStorage(String aName, String aBase64) {
        try {
            //decode the base64 string
            byte[] oledata = Base64.decode(aBase64);
            //create a temp stream to write data to
            XStream subStream = CreateTempFileStream(m_xMSF);
            XInputStream xInput = subStream.getInputStream();
            XOutputStream xOutput = subStream.getOutputStream();
            //write the length to the temp stream
            byte oleHead[] = new byte[4];
            oleHead[0] = (byte) ((oledata.length >>> 0) & 0xFF);
            oleHead[1] = (byte) ((oledata.length >>> 8) & 0xFF);
            oleHead[2] = (byte) ((oledata.length >>> 16) & 0xFF);
            oleHead[3] = (byte) ((oledata.length >>> 24) & 0xFF);
            xOutput.writeBytes(oleHead);

            // Compress the bytes
            byte[] output = new byte[oledata.length];
            Deflater compresser = new Deflater();
            compresser.setInput(oledata);
            compresser.finish();
            int compressedDataLength = compresser.deflate(output);
            //realloc the data length
            byte[] compressedBytes = new byte[compressedDataLength];
            for (int i = 0; i < compressedDataLength; i++) {
                compressedBytes[i] = output[i];
            }

            //write the compressed data to the temp stream
            xOutput.writeBytes(compressedBytes);
            //seek to 0
            XSeekable xSeek = (XSeekable) UnoRuntime.queryInterface(XSeekable.class, xInput);
            xSeek.seek(0);
            xSeek = null;
            oledata = null;

            //insert the temp stream as a sub stream and use an XTransactedObject to commit it immediately
            XTransactedObject xTransact = (XTransactedObject) UnoRuntime.queryInterface(XTransactedObject.class, m_Storage);
            m_Storage.insertByName(aName, xInput);
            xTransact.commit();
            xTransact = null;

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** separtates the uno-url into 3 different parts.
     */
    protected static String[] parseUnoUrl(String url) {
        String[] aRet = new String[3];

        if (!url.startsWith("uno:")) {
            return null;
        }

        int semicolon = url.indexOf(';');
        if (semicolon == -1) {
            return null;
        }

        aRet[0] = url.substring(4, semicolon);
        int nextSemicolon = url.indexOf(';', semicolon + 1);

        if (semicolon == -1) {
            return null;
        }
        aRet[1] = url.substring(semicolon + 1, nextSemicolon);

        aRet[2] = url.substring(nextSemicolon + 1);
        return aRet;
    }
    //connect to running OOo and keep an XConnection object so that we can disconnect from OOo as we wish
    protected XMultiServiceFactory connectAwareGetServiceFactory() throws com.sun.star.uno.Exception,
            com.sun.star.uno.RuntimeException,
            Exception {

        // Get component context
        XComponentContext xComponentContext =
                com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null);

        // instantiate connector service
        Object x = xComponentContext.getServiceManager().createInstanceWithContext(
                "com.sun.star.connection.Connector", xComponentContext);

        XConnector xConnector = (XConnector) UnoRuntime.queryInterface(XConnector.class, x);

        String a[] = parseUnoUrl(sConnectionString);
        if (null == a) {
            throw new com.sun.star.uno.Exception("Couldn't parse uno-url " + sConnectionString);
        }

        // connect using the connection string part of the uno-url only.
        m_Connection = xConnector.connect(a[0]);

        x = xComponentContext.getServiceManager().createInstanceWithContext(
                "com.sun.star.bridge.BridgeFactory", xComponentContext);

        XBridgeFactory xBridgeFactory = (XBridgeFactory) UnoRuntime.queryInterface(
                XBridgeFactory.class, x);

        // create a nameless bridge with no instance provider
        // using the middle part of the uno-url
        XBridge bridge = xBridgeFactory.createBridge("", a[1], m_Connection, null);

        // query for the XComponent interface and add this as event listener
        XComponent xComponent = (XComponent) UnoRuntime.queryInterface(
                XComponent.class, bridge);

        // get the remote instance
        x = bridge.getInstance(a[2]);

        // Did the remote server export this object ?
        if (null == x) {
            throw new com.sun.star.uno.Exception(
                    "Server didn't provide an instance for" + a[2], null);
        }

        XMultiServiceFactory xFac = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, x);
        return xFac;
    }
    protected static boolean DEBUG = false;
    protected static boolean DEBUGCHK = false;
    protected static String debugfile;

    protected static void debugln(String s) {
        debug(s + "\n");
    }

    protected static void debug(String s) {
        if (!DEBUGCHK) {
            if (System.getProperty("xsltfilter.debug") == null) {
                DEBUGCHK = true;
                return;
            } else {
                debugfile = System.getProperty("xsltfilter.debug");
                DEBUG = true;
            }
        }
        if (!DEBUG) {
            return;
        }
        try {
            FileWriter dbgwriter = new FileWriter(debugfile, true);
            dbgwriter.write(s);
            dbgwriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
