/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */
package com.sun.star.comp.helper;

import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.Any;

import com.sun.star.uno.DeploymentException;
import com.sun.star.uno.XComponentContext;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XEventListener;
import com.sun.star.lang.EventObject;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map;



class Disposer implements XEventListener
{
    private final XComponent m_xComp;


    Disposer( XComponent xComp )
    {
        m_xComp = xComp;
    }

    public void disposing( EventObject Source )
    {
        m_xComp.dispose();
    }
}

/** Component context implementation.
*/
public class ComponentContext implements XComponentContext, XComponent
{
    private static final boolean DEBUG = false;
    private static final String SMGR_NAME = "/singletons/com.sun.star.lang.theServiceManager";
    private static final String TDMGR_NAME = "/singletons/com.sun.star.reflection.theTypeDescriptionManager";

    private Map<String,Object> m_table;
    private XComponentContext m_xDelegate;

    private XMultiComponentFactory m_xSMgr;
    private boolean m_bDisposeSMgr;

    private ArrayList<XEventListener> m_eventListener;

    public ComponentContext( Hashtable<String,Object> table, XComponentContext xDelegate )
    {
        this((Map<String,Object>) table, xDelegate);
    }

    /** Ctor to create a component context passing a hashtable for values and a delegator
        reference. Entries of the passed hashtable are either direct values or
        ComponentContextEntry objects.

        @param table
               entries
        @param xDelegate
               if values are not found, request is delegated to this object
    */
    public ComponentContext( Map<String,Object> table, XComponentContext xDelegate )
    {
        m_eventListener = new ArrayList<XEventListener>();
        m_table = table;
        m_xDelegate = xDelegate;
        m_xSMgr = null;
        m_bDisposeSMgr = false;

        Object o = table.get( SMGR_NAME );
        if (o != null)
        {
            if (o instanceof ComponentContextEntry)
            {
                o = ((ComponentContextEntry)o).m_value;
            }
            m_xSMgr = UnoRuntime.queryInterface(
                XMultiComponentFactory.class, o );
        }
        if (m_xSMgr != null)
        {
            m_bDisposeSMgr = true;
        }
        else if (m_xDelegate != null)
        {
            m_xSMgr = m_xDelegate.getServiceManager();
        }

        // listen for delegate
        XComponent xComp = UnoRuntime.queryInterface(
            XComponent.class, m_xDelegate );
        if (xComp != null)
        {
            xComp.addEventListener( new Disposer( this ) );
        }
    }

    // XComponentContext impl

    public Object getValueByName( String rName )
    {
        Object o = m_table.get( rName );
        if (o == null)
        {
            if (m_xDelegate != null)
            {
                return m_xDelegate.getValueByName( rName );
            }
            else
            {
                return Any.VOID;
            }
        }

        if (!(o instanceof ComponentContextEntry))
        {
            // direct value in map
            return o;
        }

        ComponentContextEntry entry = (ComponentContextEntry)o;
        if (entry.m_lateInit == null)
        {
            return entry.m_value;
        }

        Object xInstance = null;
        try
        {
            String serviceName = (String)entry.m_lateInit;
            if (serviceName != null)
            {
                if (m_xSMgr != null)
                {
                    xInstance = m_xSMgr.createInstanceWithContext( serviceName, this );
                }
                else
                {
                    if (DEBUG)
                        System.err.println( "### no service manager instance for late init of singleton instance \"" + rName + "\"!" );
                }
            }
            else
            {
                XSingleComponentFactory xCompFac = UnoRuntime.queryInterface( XSingleComponentFactory.class, entry.m_lateInit );
                if (xCompFac != null)
                {
                    xInstance = xCompFac.createInstanceWithContext( this );
                }
                else
                {
                    if (DEBUG)
                        System.err.println( "### neither service name nor service factory given for late init of singleton instance \"" + rName + "\"!" );
                }
            }
        }
        catch (com.sun.star.uno.Exception exc)
        {
            if (DEBUG)
                System.err.println( "### exception occurred on late init of singleton instance \"" + rName + "\": " + exc.getMessage() );
        }

        if (xInstance != null)
        {
            synchronized (entry)
            {
                if (entry.m_lateInit != null)
                {
                    entry.m_value = xInstance;
                    entry.m_lateInit = null;
                }
                else // inited in the meantime
                {
                    // dispose fresh service instance
                    XComponent xComp = UnoRuntime.queryInterface(
                        XComponent.class, xInstance );
                    if (xComp != null)
                    {
                        xComp.dispose();
                    }
                }
            }
        }
        else
        {
            if (DEBUG)
                System.err.println( "### failed late init of singleton instance \"" + rName + "\"!" );
        }
        return entry.m_value;
    }

    public XMultiComponentFactory getServiceManager()
    {
        if (m_xSMgr == null)
        {
            throw new DeploymentException(
                "null component context service manager" );
        }
        return m_xSMgr;
    }

    // XComponent impl

    public void dispose()
    {
        if (DEBUG)
            System.err.print( "> disposing context " + this );

        // fire events
        EventObject evt = new EventObject( this );
        for (XEventListener listener : m_eventListener)
        {
            listener.disposing( evt );
        }
        m_eventListener.clear();

        XComponent tdmgr = null;
        // dispose values, then service manager, then typedescription manager
        for (Map.Entry<String, Object> entry : m_table.entrySet())
        {
            String name = entry.getKey();
            if (! name.equals( SMGR_NAME ))
            {
                Object o = entry.getValue();
                if (o instanceof ComponentContextEntry)
                {
                    o = ((ComponentContextEntry)o).m_value;
                }

                XComponent xComp = UnoRuntime.queryInterface( XComponent.class, o );
                if (xComp != null)
                {
                    if (name.equals( TDMGR_NAME ))
                    {
                        tdmgr = xComp;
                    }
                    else
                    {
                        xComp.dispose();
                    }
                }
            }
        }
        m_table.clear();

        // smgr
        if (m_bDisposeSMgr)
        {
            XComponent xComp = UnoRuntime.queryInterface(
                XComponent.class, m_xSMgr );
            if (xComp != null)
            {
                xComp.dispose();
            }
        }
        m_xSMgr = null;

        // tdmgr
        if (tdmgr != null)
        {
            tdmgr.dispose();
        }

        if (DEBUG)
            System.err.println( "... finished" );
    }

    public void addEventListener( XEventListener xListener )
    {
        if (xListener == null)
            throw new com.sun.star.uno.RuntimeException( "Listener must not be null" );
          if (m_eventListener.contains( xListener ))
              throw new com.sun.star.uno.RuntimeException( "Listener already registered." );

           m_eventListener.add( xListener );
    }

    public void removeEventListener( XEventListener xListener )
    {
        if (xListener == null)
            throw new com.sun.star.uno.RuntimeException( "Listener must not be null" );
          if (! m_eventListener.contains( xListener ))
              throw new com.sun.star.uno.RuntimeException( "Listener is not registered." );

        m_eventListener.remove( xListener );
    }
}
