/**************************************************************
 * 
 * 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 com.sun.star.script.framework.provider;

import com.sun.star.container.XNameContainer;

import com.sun.star.uno.Exception;
import com.sun.star.uno.XComponentContext;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XInitialization;
import com.sun.star.lang.XTypeProvider;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.frame.XModel;

import com.sun.star.util.XMacroExpander;

import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.AnyConverter;
import com.sun.star.uno.Type;

import com.sun.star.beans.XPropertySet;
import com.sun.star.beans.XVetoableChangeListener;
import com.sun.star.beans.XPropertyChangeListener;
import com.sun.star.beans.XPropertySetInfo;
import com.sun.star.beans.Property;

import com.sun.star.beans.XIntrospectionAccess;
import com.sun.star.script.XInvocation;

import com.sun.star.script.provider.XScriptContext;
import com.sun.star.script.provider.XScriptProvider;
import com.sun.star.script.provider.XScript;
import com.sun.star.script.provider.ScriptFrameworkErrorException;
import com.sun.star.script.provider.ScriptFrameworkErrorType;

import com.sun.star.script.browse.XBrowseNode;
import com.sun.star.script.browse.BrowseNodeTypes;

import com.sun.star.script.framework.log.LogUtils;

import com.sun.star.script.framework.container.ScriptMetaData;
import com.sun.star.script.framework.container.XMLParserFactory;
import com.sun.star.script.framework.container.ParcelContainer;
import com.sun.star.script.framework.container.ParsedScriptUri;
import com.sun.star.script.framework.container.UnoPkgContainer;

import com.sun.star.ucb.Command;
import com.sun.star.ucb.XContentProvider;
import com.sun.star.ucb.XContent;
import com.sun.star.ucb.XCommandProcessor;
import com.sun.star.ucb.XContentIdentifier;
import com.sun.star.ucb.XContentIdentifierFactory;

import com.sun.star.sdbc.XRow; 

import com.sun.star.script.framework.browse.ProviderBrowseNode;
import com.sun.star.script.framework.browse.DialogFactory;

import com.sun.star.deployment.XPackage;


import com.sun.star.document.XScriptInvocationContext;
import com.sun.star.frame.XTransientDocumentsDocumentContentFactory;
import com.sun.star.uno.TypeClass;

public abstract class ScriptProvider
    implements XScriptProvider, XBrowseNode, XPropertySet, XInvocation,
               XInitialization, XTypeProvider, XServiceInfo, XNameContainer
{
    private final String[] __serviceNames = {
        "com.sun.star.script.provider.ScriptProviderFor",
        "com.sun.star.script.provider.LanguageScriptProvider"
    };

    public final static String CLASSPATH = "classpath";

    protected String language;

    protected XComponentContext m_xContext;
    protected XMultiComponentFactory m_xMultiComponentFactory;
    protected XModel m_xModel;
    protected XScriptInvocationContext m_xInvocContext;
    protected ParcelContainer m_container;

    // proxies to helper objects which implement interfaces
    private XPropertySet m_xPropertySetProxy;
    private XInvocation m_xInvocationProxy;
   // TODO should this be implemented in this class
    private XBrowseNode m_xBrowseNodeProxy;
    private XScriptContext m_xScriptContext;
 
    public ScriptProvider( XComponentContext ctx, String language )
    {
        this.language = language;
        __serviceNames[0] += language;

        LogUtils.DEBUG( "ScriptProvider: constructor - start. " + language );

        m_xContext = ctx;

        // Initialize DialogFactory class in case dialogs are required
        DialogFactory.createDialogFactory(m_xContext);

        try
        {
            m_xMultiComponentFactory = m_xContext.getServiceManager();

            if ( m_xMultiComponentFactory == null )
            {
                throw new Exception( "Error could not obtain a " +
                    "multicomponent factory - rethrowing Exception." );
            }

            Object serviceObj = m_xContext.getValueByName(
                "/singletons/com.sun.star.util.theMacroExpander");

            XMacroExpander me = (XMacroExpander) AnyConverter.toObject(
                new Type(XMacroExpander.class), serviceObj);

            XMLParserFactory.setOfficeDTDURL(me.expandMacros(
                "${$OOO_BASE_DIR/program/bootstraprc::BaseInstallation}/share/dtd/officedocument/1_0/"));
            
        }
        catch ( Exception e )
        {
            LogUtils.DEBUG( LogUtils.getTrace( e ) );
            throw new com.sun.star.uno.RuntimeException(
                "Error constructing  ScriptProvider: "
                + e.getMessage() );
        }

        LogUtils.DEBUG( "ScriptProvider: constructor - finished." );
    }

    synchronized public XScriptContext getScriptingContext()
    {
        if ( m_xScriptContext == null )
        {
            m_xScriptContext = ScriptContext.createContext( m_xModel, m_xInvocContext, m_xContext, m_xMultiComponentFactory );
        }
        return m_xScriptContext;
    }
    public void initialize( Object[] aArguments ) 
        throws com.sun.star.uno.Exception
    {
        LogUtils.DEBUG( "entering XInit for language " + language);
        boolean isPkgProvider = false;
        if( aArguments.length == 1 )
        {
            String contextUrl = null;
            if ( AnyConverter.getType(aArguments[0]).getTypeClass().equals(TypeClass.INTERFACE) )
            {
                // try whether it denotes a XScriptInvocationContext
                m_xInvocContext = (XScriptInvocationContext)UnoRuntime.queryInterface(
                        XScriptInvocationContext.class, aArguments[0]);
                if ( m_xInvocContext != null )
                {
                    // if so, obtain the document - by definition, this must be
                    // the ScriptContainer
                    m_xModel = (XModel)UnoRuntime.queryInterface( XModel.class,
                            m_xInvocContext.getScriptContainer() );
                }
                else
                {
                    // otherwise, check whether it's an XModel
                    m_xModel = (XModel)UnoRuntime.queryInterface( XModel.class,
                            m_xInvocContext.getScriptContainer() );
                }
                if ( m_xModel == null )
                {
                    throw new com.sun.star.uno.Exception(
                        "ScriptProvider argument must be either a string, a valid XScriptInvocationContext, " +
                        "or an XModel", this);
                }

                contextUrl = getDocUrlFromModel( m_xModel );
                m_container = new ParcelContainer( m_xContext, contextUrl, language  );
            }
            else if (AnyConverter.isString(aArguments[0]) == true)
            {
                String originalContextURL = AnyConverter.toString(aArguments[0]);
                LogUtils.DEBUG("creating Application, path: " + originalContextURL );
                contextUrl = originalContextURL;
                // TODO no support for packages in documents yet
                if ( originalContextURL.startsWith( "vnd.sun.star.tdoc" ) )
                {
                    m_container = new ParcelContainer( m_xContext, contextUrl, language  );
                    m_xModel = getModelFromDocUrl( originalContextURL );
                }
                else
                {
                    String extensionDb = null;
                    String extensionRepository = null;
                    if ( originalContextURL.startsWith( "bundled" ) )
                    {
                        contextUrl = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS";
                        extensionDb = "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + PathUtils.BOOTSTRAP_NAME + "::UserInstallation}/user";
                        extensionRepository = "bundled";
                    }

                    if ( originalContextURL.startsWith( "share" ) )
                    {
                        contextUrl = "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + PathUtils.BOOTSTRAP_NAME + "::BaseInstallation}/share";
                        extensionDb = "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + PathUtils.BOOTSTRAP_NAME + "::UserInstallation}/user";
                        extensionRepository = "shared";
                    }
                    else if ( originalContextURL.startsWith( "user" ) )
                    {
                        contextUrl = "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + PathUtils.BOOTSTRAP_NAME + "::UserInstallation}/user";
                        extensionDb = "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + PathUtils.BOOTSTRAP_NAME + "::UserInstallation}/user";
                        extensionRepository = "user";
                    }
                    
                    if ( originalContextURL.endsWith( "uno_packages") )
                    {
                        isPkgProvider = true;
                    }
                    if ( originalContextURL.endsWith( "uno_packages") &&  !originalContextURL.equals( contextUrl  )
                            && !extensionRepository.equals("bundled"))
                    {
                        contextUrl = PathUtils.make_url( contextUrl, "uno_packages"  );
                    }
                    if ( isPkgProvider )
                    {
                        m_container = new UnoPkgContainer( m_xContext, contextUrl, extensionDb, extensionRepository, language  );
                    }
                    else
                    {
                        m_container = new ParcelContainer( m_xContext, contextUrl, language  ); 
                    }
                }
            }
            else
            {
                throw new com.sun.star.uno.RuntimeException(
                    "ScriptProvider created with invalid argument");
            }

            LogUtils.DEBUG("Modified Application path is: " + contextUrl );
            LogUtils.DEBUG("isPkgProvider is: " + isPkgProvider );

            // TODO should all be done in this class instead of
            // deleagation????
            m_xBrowseNodeProxy = new ProviderBrowseNode( this, 
                m_container, m_xContext );

            m_xInvocationProxy = (XInvocation)UnoRuntime.queryInterface(XInvocation.class, m_xBrowseNodeProxy);
            m_xPropertySetProxy = (XPropertySet)UnoRuntime.queryInterface(XPropertySet.class, m_xBrowseNodeProxy);
        }
        else
        {
            // this is ok, for example when executing a script from the
            // command line
            LogUtils.DEBUG( "ScriptProviderFor" + language +
                " initialized without a context");
        }
        LogUtils.DEBUG( "leaving XInit" );
    }

    /**
     *  Gets the types attribute of the ScriptProvider object
     *
     * @return    The types value
     */
    public com.sun.star.uno.Type[] getTypes()
    {
        Type[] retValue = new Type[ 8 ];
        retValue[ 0 ] = new Type( XScriptProvider.class );
        retValue[ 1 ] = new Type( XBrowseNode.class );
        retValue[ 2 ] = new Type( XInitialization.class );
        retValue[ 3 ] = new Type( XTypeProvider.class );
        retValue[ 4 ] = new Type( XServiceInfo.class );
        retValue[ 5 ] = new Type( XPropertySet.class );
        retValue[ 6 ] = new Type( XInvocation.class );
        retValue[ 7 ] = new Type( com.sun.star.container.XNameContainer.class );
        return retValue;
    }

    /**
     *  Gets the implementationId attribute of the ScriptProvider object
     *
     * @return    The implementationId value
     */
    public byte[] getImplementationId()
    {
        return this.getClass().getName().getBytes();
    }

    /**
     *  Gets the implementationName attribute of the ScriptProvider object
     *
     * @return    The implementationName value
     */
    public String getImplementationName()
    {
        return getClass().getName();
    }

    /**
     *  Description of the Method
     *
     * @param  serviceName  Description of the Parameter
     * @return              Description of the Return Value
     */
    public boolean supportsService( String serviceName )
    {
        for ( int index = __serviceNames.length; index-- > 0; )
        {
            if ( serviceName.equals( __serviceNames[ index ]  ) )
            {
                return true;
            }
        }
        return false;
    }

    /**
     *  Gets the supportedServiceNames attribute of the ScriptProvider object
     *
     * @return    The supportedServiceNames value
     */
    public String[] getSupportedServiceNames()
    {
        return __serviceNames;
    }


   
    public abstract XScript getScript( /*IN*/String scriptURI )
        throws com.sun.star.uno.RuntimeException,
               ScriptFrameworkErrorException;

    // TODO need to encapsulate this better,
    // Some factory concept for creating/accessing Editor
    // How this is passed down or how it is accessable by BrowseNode
    // implementations needs thinking about
    // This method is used to determine whether the ScriptProvider
    // has a ScriptEditor
    public abstract boolean hasScriptEditor();
    // TODO see above
    // This method is used to get the ScriptEditor for this ScriptProvider
    public abstract ScriptEditor getScriptEditor();

    public ScriptMetaData  getScriptData( /*IN*/String scriptURI ) throws ScriptFrameworkErrorException

    {
        ParsedScriptUri details = null;
        try
        {
            details = m_container.parseScriptUri( scriptURI );
            ScriptMetaData scriptData = m_container.findScript( details );
            if ( scriptData == null )
            {
                throw new ScriptFrameworkErrorException( details.function + " does not exist", 
                    null, details.function, language, ScriptFrameworkErrorType.NO_SUCH_SCRIPT );
            }
            return scriptData;
        }
        catch (  com.sun.star.lang.IllegalArgumentException ila )
        {
            // TODO specify the correct error Type
            throw new ScriptFrameworkErrorException( ila.getMessage(), 
                null, scriptURI, language, ScriptFrameworkErrorType.UNKNOWN );
        }
        catch ( com.sun.star.container.NoSuchElementException nse )
        {
            throw new ScriptFrameworkErrorException( nse.getMessage(), 
                null, details.function, language, ScriptFrameworkErrorType.NO_SUCH_SCRIPT );
        }
        catch ( com.sun.star.lang.WrappedTargetException wta )
        {
            // TODO specify the correct error Type
            Exception wrapped = (Exception)wta.TargetException;
            String message = wta.getMessage();
            if ( wrapped != null )
            {
                message = wrapped.getMessage();
            }
            throw new ScriptFrameworkErrorException( message, 
                null, details.function, language, ScriptFrameworkErrorType.UNKNOWN );
        }

    }


    // Implementation of XBrowseNode interface
    public String getName()
    {
        return language;
    }

    public XBrowseNode[] getChildNodes()
    {
        if ( m_xBrowseNodeProxy  == null )
        {
            LogUtils.DEBUG("No Nodes available ");
            return new XBrowseNode[0];
        }
        return m_xBrowseNodeProxy .getChildNodes();
    }

    public boolean hasChildNodes()
    {
        if (  m_xBrowseNodeProxy == null )
        {
            LogUtils.DEBUG("No Nodes available ");
            return false;
        }
        return  m_xBrowseNodeProxy.hasChildNodes();
    }

    public short getType()
    {
        return BrowseNodeTypes.CONTAINER;
    }

    public String toString()
    {
        return getName();
    }

    // implementation of XInvocation interface
    public XIntrospectionAccess getIntrospection() {
        return m_xInvocationProxy.getIntrospection();
    }

    public Object invoke(String aFunctionName, Object[] aParams,
                         short[][] aOutParamIndex, Object[][] aOutParam)
        throws com.sun.star.lang.IllegalArgumentException,
               com.sun.star.script.CannotConvertException,
               com.sun.star.reflection.InvocationTargetException
    {
        return m_xInvocationProxy.invoke(
            aFunctionName, aParams, aOutParamIndex, aOutParam);
    }

    public void setValue(String aPropertyName, Object aValue)
        throws com.sun.star.beans.UnknownPropertyException,
               com.sun.star.script.CannotConvertException,
               com.sun.star.reflection.InvocationTargetException
    {
        m_xInvocationProxy.setValue(aPropertyName, aValue);
    }

    public Object getValue(String aPropertyName)
        throws com.sun.star.beans.UnknownPropertyException
    {
        return m_xInvocationProxy.getValue(aPropertyName);
    }

    public boolean hasMethod(String aName) {
        return m_xInvocationProxy.hasMethod(aName);
    }

    public boolean hasProperty(String aName) {
        return m_xInvocationProxy.hasProperty(aName);
    }

    public XPropertySetInfo getPropertySetInfo()
    {
        return m_xPropertySetProxy.getPropertySetInfo();
    }

    public void setPropertyValue(String aPropertyName, Object aValue)
        throws com.sun.star.beans.UnknownPropertyException,
               com.sun.star.beans.PropertyVetoException,
               com.sun.star.lang.IllegalArgumentException,
               com.sun.star.lang.WrappedTargetException
    {
        m_xPropertySetProxy.setPropertyValue(aPropertyName, aValue);
    }

    public Object getPropertyValue(String PropertyName)
        throws com.sun.star.beans.UnknownPropertyException,
               com.sun.star.lang.WrappedTargetException
    {
        return m_xPropertySetProxy.getPropertyValue(PropertyName);
    }

    public void addPropertyChangeListener(
        String aPropertyName, XPropertyChangeListener xListener)
        throws com.sun.star.beans.UnknownPropertyException,
               com.sun.star.lang.WrappedTargetException
    {
        m_xPropertySetProxy.addPropertyChangeListener(aPropertyName, xListener);
    }

    public void removePropertyChangeListener(
        String aPropertyName, XPropertyChangeListener aListener)
        throws com.sun.star.beans.UnknownPropertyException,
               com.sun.star.lang.WrappedTargetException
    {
        m_xPropertySetProxy.removePropertyChangeListener(
            aPropertyName, aListener);
    }

    public void addVetoableChangeListener(
        String PropertyName, XVetoableChangeListener aListener)
        throws com.sun.star.beans.UnknownPropertyException,
               com.sun.star.lang.WrappedTargetException
    {
        m_xPropertySetProxy.addVetoableChangeListener(PropertyName, aListener);
    }

    public void removeVetoableChangeListener(
        String PropertyName, XVetoableChangeListener aListener)
        throws com.sun.star.beans.UnknownPropertyException,
               com.sun.star.lang.WrappedTargetException
    {
        m_xPropertySetProxy.removeVetoableChangeListener(
            PropertyName, aListener);
    }
    public java.lang.Object getByName( String aName ) throws com.sun.star.container.NoSuchElementException, com.sun.star.lang.WrappedTargetException
    {
        // TODO needs implementing?
        if ( true )
        { 
            throw new com.sun.star.uno.RuntimeException(
                "getByName not implemented" );           
        }
        return new Object();
    }

    public String[] getElementNames()
    {
        // TODO needs implementing?
        String[] result = new String[0];
        if ( true )
        { 
            throw new com.sun.star.uno.RuntimeException(
                "getElementNames not implemented" );           
                
        }
        return result;
    }
  

    // Performs the getRegStatus functionality for the PkgMgr 
    public boolean hasByName( String aName )
    {
        boolean result = false;
        if ( ((UnoPkgContainer)m_container).hasRegisteredUnoPkgContainer( aName ) )
        {
            result = true;
        }
        return result;
    }

    public com.sun.star.uno.Type getElementType() 
    {
        // TODO at the moment this returns void indicating
        // type is unknown should indicate XPackage ? do we implement XPackage
        return new Type();
    }

    public boolean hasElements()
    {
        // TODO needs implementing?
        boolean result = false;
        if ( true )
        { 
            throw new com.sun.star.uno.RuntimeException(
                "hasElements not implemented" );           
                
        }
        return result;
    }
    public void replaceByName( String aName, java.lang.Object aElement ) throws com.sun.star.lang.IllegalArgumentException, com.sun.star.container.NoSuchElementException, com.sun.star.lang.WrappedTargetException
    {
        // TODO needs implementing
        if ( true )
        { 
            throw new com.sun.star.uno.RuntimeException(
                "replaceByName not implemented" );           
                
        }
    }

    public void insertByName( String aName, java.lang.Object aElement ) throws com.sun.star.lang.IllegalArgumentException, com.sun.star.container.ElementExistException, com.sun.star.lang.WrappedTargetException
    {
        LogUtils.DEBUG("Provider for " + language + " received register for package " + aName );
        XPackage newPackage = ( XPackage ) UnoRuntime.queryInterface( XPackage.class, aElement );
        if ( aName.length() == 0 )
        {
            throw new  com.sun.star.lang.IllegalArgumentException( "Empty name" ); 
        }
        if ( newPackage == null )
        {
            throw new com.sun.star.lang.IllegalArgumentException( "No package supplied" ); 
        }

        ((UnoPkgContainer)m_container).processUnoPackage( newPackage, language  );
    }

    // de-register for library only !!
    public void removeByName( String Name ) throws com.sun.star.container.NoSuchElementException, com.sun.star.lang.WrappedTargetException
    {
        LogUtils.DEBUG("In ScriptProvider.removeByName() for " + Name + " this provider = " + language );
        ParcelContainer c = ((UnoPkgContainer)m_container).getRegisteredUnoPkgContainer( Name );
        if ( c != null )
        {
            String libName;
            if (Name.endsWith("/"))
            {
                String tmp = Name.substring( 0, Name.lastIndexOf( "/" ) );
                libName = tmp.substring( tmp.lastIndexOf( "/" ) + 1 );
            }
            else
            {
                libName = Name.substring( Name.lastIndexOf( "/" ) + 1 );
            }
            LogUtils.DEBUG("Deregistering library " + libName );
            if ( c.removeParcel( libName ) )
            {
                ((UnoPkgContainer)m_container).deRegisterPackageContainer( Name ); 
            }
            else 
            {
                throw new com.sun.star.container.NoSuchElementException( libName + " cannot be removed from container." );
            }
        }
        else
        {
            throw new com.sun.star.container.NoSuchElementException( Name + " doesn't exist for " + language );
        }
        // TODO see if we want to remove the ParcelContainer is no Parcels/Libraries left
    }

    private String getDocUrlFromModel( XModel document )
    {
        XTransientDocumentsDocumentContentFactory factory = null;
        try
        {
            factory = (XTransientDocumentsDocumentContentFactory)UnoRuntime.queryInterface(
                    XTransientDocumentsDocumentContentFactory.class,
                        m_xMultiComponentFactory.createInstanceWithContext(
                            "com.sun.star.frame.TransientDocumentsDocumentContentFactory",
                            m_xContext
                        )
                    );
        }
        catch (Exception ex)
        {
        }

        if ( factory == null )
            throw new com.sun.star.uno.RuntimeException( "ScriptProvider: unable to create a TDOC context factory.", this );

        try
        {
            XContent content = factory.createDocumentContent( document );
            return content.getIdentifier().getContentIdentifier();
        }
        catch( Exception ex )
        {
        }

        LogUtils.DEBUG("unable to determine the model's TDOC URL");
        return "";
    }
    
    private  XModel getModelFromDocUrl( String docUrl )
    {
        LogUtils.DEBUG("getModelFromDocUrl - searching for match for ->" + docUrl + "<-" );
        XModel xModel = null;
        try
        { 
            Object[] args = new String[] {"Local", "Office" };
        
            Object ucb = m_xMultiComponentFactory.createInstanceWithArgumentsAndContext( "com.sun.star.ucb.UniversalContentBroker", args, m_xContext );


            XContentIdentifierFactory xFac  =  ( XContentIdentifierFactory )
                UnoRuntime.queryInterface( XContentIdentifierFactory.class,
                    ucb );


            XContentIdentifier xCntId = xFac.createContentIdentifier( docUrl );


            XContentProvider xCntAccess = ( XContentProvider )
                UnoRuntime.queryInterface( XContentProvider.class,
                    ucb );
            

            XContent xCnt = xCntAccess.queryContent( xCntId ); 


            XCommandProcessor xCmd = ( XCommandProcessor )
                UnoRuntime.queryInterface( XCommandProcessor.class, xCnt ); 

    
            Property[] pArgs = new Property[ ] { new Property() };
            pArgs[ 0 ].Name = "DocumentModel";
            pArgs[ 0 ].Handle = -1;
    
            Command command = new Command();

            command.Handle = -1;
            command.Name = "getPropertyValues";
            command.Argument = pArgs;

            com.sun.star.ucb.XCommandEnvironment env = null ;
            Object result =  xCmd.execute( command, 0, env ) ; 

            XRow values = ( XRow ) UnoRuntime.queryInterface( XRow.class, 
                result ); 
    
            xModel = ( XModel )  UnoRuntime.queryInterface( XModel.class,
                values.getObject( 1, null ) );
        }
        catch ( Exception ignore )
        {
            LogUtils.DEBUG("Failed to get model exception " + ignore );
          
        }
        return xModel;
    }


}
