/*
* Copyright (c) 2002 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

package com.nokia.microedition.media;

import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import java.util.Enumeration;
import javax.microedition.media.protocol.DataSource;
import javax.microedition.media.TimeBase;
import javax.microedition.media.Player;
import javax.microedition.media.MediaException;
//import com.symbian.lcdjava.lang.ObjectExtensions;
//import com.symbian.epoc.events.MIDEventServer;
//import com.symbian.midp.runtime.ToolkitInvoker;
//import com.symbian.midp.runtime.ToolkitObserver;

import com.nokia.microedition.media.animation.AnimationPlayerFactory;
import com.nokia.microedition.media.protocol.ProtocolFactory;
import com.nokia.microedition.media.tone.PlayToneImpl;

import com.nokia.microedition.volumekeys.ForegroundListener;
import com.nokia.mj.impl.rt.support.Finalizer;

//To get the shutdown event from the midlet
import com.nokia.mj.impl.rt.support.ApplicationUtils;
import com.nokia.mj.impl.rt.support.ApplicationInfo;
import com.nokia.mj.impl.rt.support.ShutdownListener;
import com.nokia.mj.impl.utils.Logger;

/**
 * ManagerImpl implements the functionality specified in
 * javax.microedition.media.Manager class.
 * This class is a singleton and instance can be got with getInstance method.
 */
public class ManagerImpl implements PlugIn
{
    // ManagerImpl native instance
    private static int sManagerHandle;

    // CMMAEventSource
    private static int iFunctionSourceHandle;

    // Static instance, can be got with getInstace method
    private static ManagerImpl sManager;

    private final TimeBase iSystemTimeBase = new SystemTimeBase();

    private final Vector iPlugIns = new Vector();
    private final ProtocolFactory iProtocolFactory = new ProtocolFactory();
    private final ForegroundListener iForegroundListener;
    private Finalizer iFinalizer;

    Finalizer registerForFinalization()
    {
        return new Finalizer()
        {
            public void finalizeImpl()
            {
                doFinalize();
            }
        };
    }

    // Play tone implementation
    private PlayToneImpl iPlayToneImpl = new PlayToneImpl();

    static
    {
        // This is called when class is loaded for the first time
        sManager = new ManagerImpl();

        try
        {
            // Invoke external components
            Setup.setup(iFunctionSourceHandle);
        }
        catch (OutOfMemoryError oome)
        {
            // External setup failed clean MMA native resources and throw oome
            sManager.doFinalize();
            throw oome;
        }
    }

    /**
     * This private constructor can be called only from staic block.
     */
    private ManagerImpl()
    {
        // Will cause registeredFinalize() to be called when ObjectExtensions
        // is finalized.
        Logger.LOG(Logger.EJavaMMAPI, Logger.EInfo,"ManagerImpl constructor before loading library");
        com.nokia.mj.impl.rt.support.Jvm.loadSystemLibrary("javamobilemedia");
        Logger.LOG(Logger.EJavaMMAPI, Logger.EInfo,"ManagerImpl constructor library loaded.. going to register for finalizer");
        //ObjectExtensions.registerForFinalization(this);
        iFinalizer = registerForFinalization();
        Logger.LOG(Logger.EJavaMMAPI, Logger.EInfo,"ManagerImpl constructor ... after registering for finalizer");

        // Event server contructor needs new String object,
        // otherwise it don't work..

        iFunctionSourceHandle = _createEventSource();
        Logger.LOG(Logger.EJavaMMAPI, Logger.EInfo,"ManagerImpl.java : iFunctionSourceHandle is " + iFunctionSourceHandle);
        if (iFunctionSourceHandle < NativeError.KErrNone)
        {
            throw new OutOfMemoryError("SymbianOS error: " + iFunctionSourceHandle);
        }

        sManagerHandle = _createManager(iFunctionSourceHandle,
                                        ApplicationInfo.getInstance().getSuiteUid().hashCode());
        if (sManagerHandle < NativeError.KErrNone)
        {
            throw new OutOfMemoryError();
        }
        //Use ShutdownListener to get notification of exit and release the resource
        //MMAPI UI 3.x work

        setShutdownListener();
        // support for gif animation player
        iPlugIns
        .addElement(new com.nokia.microedition.media.animation.AnimationPlayerFactory());

        // ManagerImpl is also a PlugIn that getAllSupportedContentTypes,
        // getAllSupportedProtocols and createPlayer methods can be used
        // through PlugIn interface.
        iPlugIns.addElement(this);

        // support for device://tone and jts
        iPlugIns.addElement(
            new com.nokia.microedition.media.protocol.device.tone.Protocol());

        // Create foreground listener which listens the state of the midlet
        // This feature is a part of the media keys feature so it is flagged
        Logger.LOG(Logger.EJavaMMAPI, Logger.EInfo,"before constructing ForegroundListener....");
        iForegroundListener = new ForegroundListener(iFunctionSourceHandle);
        iForegroundListener.init();
    }


    private void setShutdownListener()
    {
        // Get the insatnce of ApplicationUtils.
        ApplicationUtils appUtils = ApplicationUtils.getInstance();

        // Get the name of the application.
        appUtils.addShutdownListener(new ShutdownListener()
        {
            //The method that gets called when Application is shutting down
            public void shuttingDown()
            {

                doFinalize();


            }
        });

    }

    /**
     * Returns MMA event source handle
     */
    public static int getEventSource()
    {
        return iFunctionSourceHandle;
    }
    /**
     * Returns native handle to Manager
     */
    public static int getHandle()
    {
        return sManagerHandle;
    }

    /**
     * Return ManagerImpl instance
     */
    public static ManagerImpl getInstance()
    {
        return sManager;
    }

    /**
     * Adds new PlugIn to PlugIns array.
     * @param aPlugIn New PlugIn.
     */
    public void addPlugIn(PlugIn aPlugIn)
    {
        iPlugIns.addElement(aPlugIn);
    }




    /**
     * This will be called when ObjectExtensions is finalized.
     */
    synchronized final void doFinalize()
    {
        _dispose(iFunctionSourceHandle);
        iFunctionSourceHandle = 0;
    }

    /**
     * This method is called in Toolkit's destroyNotify call.
     * This will release convenient native resources. All native resource
     * will be deleted in registeredFinalize() method.
     */
    synchronized final void release()
    {
        //   _release(iFunctionSourceHandle);
    }

    /**
     * Create String array from Vector and remove String duplicates.
     * @param aVector Vector containing String objects.
     */
    private String[] createStringArray(Vector aVector)
    {
        // remove all duplicates from the vector
        for (int i = 0; i < aVector.size(); i++)
        {
            String element = (String)aVector.elementAt(i);
            for (int j = i + 1; j < aVector.size();)
            {
                if (element.equals((String)aVector.elementAt(j)))
                {
                    aVector.removeElementAt(j);
                }
                else
                {
                    j++;
                }
            }
        }

        // Create new array for vector elements and copy elements
        String[] s = new String[ aVector.size()];
        aVector.copyInto(s);
        return s;
    }

    /**
     * Return the list of supported content types for the given protocol.
     * <p>
     * See <a href="#content-type">content types</a> for the syntax
     * of the content types returned.
     * See <a href="#media-protocol">protocol name</a> for the syntax
     * of the protocol used.
     * <p>
     * For example, if the given <code>protocol</code>
     * is <code>"http"</code>,
     * then the supported content types that can be played back
     * with the <code>http</code> protocol will be returned.
     * <p>
     * If <code>null</code> is passed in as the <code>protocol</code>,
     * all the supported content types for this implementation
     * will be returned.  The returned array must be non-empty.
     * <p>
     * If the given <code>protocol</code> is an invalid or
     * unsupported protocol, then an empty array will be returned.
     *
     * @param aProtocol The input protocol for the supported content types.
     * @return The list of supported content types for the given protocol.
     */
    public String[] getAllSupportedContentTypes(String aProtocol)
    {
        if ((aProtocol != null) && (aProtocol.length() == 0))
        {
            // No supported types for 0 length string.
            return new String[ 0 ];
        }
        Vector contentTypes = new Vector();

        Enumeration plugIns = iPlugIns.elements();

        // go through all plugins and get content types
        while (plugIns.hasMoreElements())
        {
            PlugIn plugIn = (PlugIn)plugIns.nextElement();
            String[] types = plugIn.getSupportedContentTypes(aProtocol);

            // Add all types to vector
            for (int i = 0; i < types.length; i++)
            {
                contentTypes.addElement(types[ i ]);
            }
        }
        return createStringArray(contentTypes);
    }

    /**
     * Return the list of supported protocols given the content
     * type.  The protocols are returned
     * as strings which identify what locators can be used for creating
     * <code>Player</code>'s.
     * <p>
     * See <a href="#media-protocol">protocol name</a> for the syntax
     * of the protocols returned.
     * See <a href="#content-type">content types</a> for the syntax
     * of the content type used.
     * <p>
     * For example, if the given <code>content_type</code>
     * is <code>"audio/x-wav"</code>, then the supported protocols
     * that can be used to play back <code>audio/x-wav</code>
     * will be returned.
     * <p>
     * If <code>null</code> is passed in as the
     * <code>content_type</code>,
     * all the supported protocols for this implementation
     * will be returned.  The returned array must be non-empty.
     * <p>
     * If the given <code>content_type</code> is an invalid or
     * unsupported content type, then an empty array will be returned.
     *
     * @param aContentType The content type for the supported protocols.
     * @return The list of supported protocols for the given content type.
     */
    public String[] getAllSupportedProtocols(String aContentType)
    {
        String contentType = aContentType;
        if ((contentType != null) &&  contentType.equals(""))
        {
            return new String[ 0 ];
        }

        Vector protocols = new Vector();
        Enumeration plugIns = iPlugIns.elements();
        while (plugIns.hasMoreElements())
        {
            PlugIn plugIn = (PlugIn)plugIns.nextElement();
            String[] types = plugIn.getSupportedProtocols(aContentType);
            for (int i = 0; i < types.length; i++)
            {
                protocols.addElement(types[ i ]);
            }
        }
        return createStringArray(protocols);
    }

    /**
     * From PlugIn. Get MMA supported protocols.
     */
    public String[] getSupportedProtocols(String aContentType)
    {
        String[] protocols = _getSupportedProtocols(iFunctionSourceHandle,
                             sManagerHandle,
                             aContentType);
        NativeError.checkOOM(protocols);
        return protocols;
    }

    /**
     * From PlugIn. Get MMA supported types.
     */
    public String[] getSupportedContentTypes(String aProtocol)
    {
        String[] types = _getSupportedContentTypes(iFunctionSourceHandle,
                         sManagerHandle,
                         aProtocol);
        NativeError.checkOOM(types);
        return types;
    }

    /**
     * From PlugIn.
     */
    public InternalPlayer createPlayer(DataSource aSource)
    throws MediaException, IOException
    {
        InternalPlayer player = null;
        if (aSource.getContentType() != null)
        {
            // Create player from content type
            if (isValidContentType(aSource.getContentType()))
            {
                player = NativePlayerFactory.createPlayer(aSource.getContentType(),
                         aSource);
            }
            else
            {
                throw new MediaException(
                    "Content type not supported: " + aSource.getContentType());
            }
        }

        if ((player == null) &&
                (aSource.getLocator() != null))
        {
            // Create player from locator
            player = NativePlayerFactory.createPlayer(
                         new Locator(aSource.getLocator()),
                         aSource);
        }

        if (player == null)
        {
            // Could not create player from content-type or locator,
            // try to create player from header data
            player = NativePlayerFactory.createPlayer(
                         ((BufferDataSource)aSource).getHeader(),
                         aSource);
        }

        return player;
    }

    /**
     * From PlugIn. Empty implemation.
     */
    public void preparePlayer(InternalPlayer aPlayer) throws MediaException
    {
    }

    /**
     * This method calls preparePlayer to all PlugIns.
     */
    private void pluginsPreparePlayer(InternalPlayer aPlayer)
    throws MediaException
    {


        // Call preparePlayer to all plugins
        Enumeration plugins = iPlugIns.elements();
        while (plugins.hasMoreElements())
        {
            ((PlugIn)plugins.nextElement()).preparePlayer(aPlayer);
        }
    }

    /**
     * Create a <code>Player</code> from an input locator.
     *
     * @param aLocator A locator string in URI syntax that describes
     * the media content.
     * @return A new <code>Player</code>.
     * @exception IllegalArgumentException Thrown if <code>locator</code>
     * is <code>null</code>.
     * @exception MediaException Thrown if a <code>Player</code> cannot
     * be created for the given locator.
     * @exception IOException Thrown if there was a problem connecting
     * with the source pointed to by the <code>locator</code>.
     * @exception SecurityException Thrown if the caller does not
     * have security permission to create the <code>Player</code>.
     */
    public Player createPlayer(String aLocator)
    throws IOException, MediaException
    {
        if (aLocator == null)
        {
            throw new IllegalArgumentException("Locator is null.");
        }
        InternalPlayer player =null;
        /// Implementation done for java ui 3.x req
        // in case of AnimationPlayer, we won't be using the ProtocolFactory class.
        //
        Enumeration plugins = iPlugIns.elements();
        AnimationPlayerFactory apf=null;
        while (plugins.hasMoreElements() && (player == null))
        {
            PlugIn temp = (PlugIn) plugins.nextElement();
            if (temp instanceof AnimationPlayerFactory)
            {
                apf = (AnimationPlayerFactory) temp;
                break;
            }
        }
        if (apf!=null)
        {
            player=apf.createPlayer(aLocator);
        }
        ////////////////////////////////////////////////////////
        // if player is still null, try to create the native player
        if (player==null)
            player =iProtocolFactory.createPlayer(
                        new Locator(aLocator));
        if (player == null)
        {
            throw new MediaException("Locator not supported: " +
                                     aLocator);
        }
        pluginsPreparePlayer(player);
        return player;
    }

    /**
     * Create a <code>InternalPlayer</code> for a <code>DataSource</code>.
     */
    public InternalPlayer createInternalPlayer(DataSource aSource)
    throws IOException, MediaException
    {
        // Throw IllegalArgumentException if source  is null.
        if (aSource == null)
        {
            throw new IllegalArgumentException("DataSource is null.");
        }
        aSource.connect(); // Ensure that external source is connected.
        if (aSource.getStreams() == null ||
                aSource.getStreams().length == 0)
        {
            // There must be atleast one stream in the DataSource
            throw new MediaException(
                "There must be at least one stream in datasource");
        }

        BufferDataSource bdc = null;
        if (aSource instanceof BufferDataSource)
        {
            bdc = (BufferDataSource)aSource;
        }
        else
        {
            bdc = new BufferDataSource(aSource);
        }

        InternalPlayer player = null;
        Enumeration plugins = iPlugIns.elements();
        // Loop through all plugins, stop if player was created
        while (plugins.hasMoreElements() &&
                (player == null))
        {
            PlugIn tmp = (PlugIn)plugins.nextElement();
            player = tmp.createPlayer(bdc);
        }

        if (player == null)
        {
            // MMA or plugins could not create player
            bdc.disconnect();

            throw new MediaException("Could not create player.");
        }

        return player;
    }

    /**
     * Create a <code>Player</code> to play back media from an
     * <code>InputStream</code>.
     * <p>
     * The <code>type</code> argument
     * specifies the content-type of the input media.  If
     * <code>null</code> is given, <code>Manager</code> will
     * attempt to determine the type.  However, since determining
     * the media type is non-trivial for some media types, it
     * may not be feasible in some cases.  The
     * <code>Manager</code> may throw a <code>MediaException</code>
     * to indicate that.
     *
     * @param aStream The <code>InputStream</code> that delivers the
     * input media.
     * @param aType The <code>ContentType</code> of the media.
     * @return A new <code>Player</code>.
     * @exception IllegalArgumentException Thrown if <code>stream</code>
     * is <code>null</code>.
     * @exception MediaException Thrown if a <code>Player</code> cannot
     * be created for the given stream and type.
     * @exception IOException Thrown if there was a problem reading data
     * from the <code>InputStream</code>.
     * @exception SecurityException Thrown if the caller does not
     * have security permission to create the <code>Player</code>.
     */
    public Player createPlayer(InputStream aStream, String aType)
    throws IOException, MediaException
    {
        if (aStream == null)
        {
            throw new IllegalArgumentException("InputStream is null.");
        }

        InputStreamSourceStream sourceStream =
            new InputStreamSourceStream(aStream);

        // Create data source without locator.
        DataSource dataSource = new InputStreamDataSource(sourceStream,
                aType);
        InternalPlayer player = createInternalPlayer(dataSource);

        if (player != null)
        {
            // Call preparePlayer to all plugins
            pluginsPreparePlayer(player);
        }

        return player;
    }

    /**
     * Play back a tone as specified by a note and its duration.
     * A note is given in the range of 0 to 127 inclusive.  The frequency
     * of the note can be calculated from the following formula:
     * <pre>
     *     SEMITONE_CONST = 17.31234049066755 = 1/(ln(2^(1/12)))
     *     note = ln(freq/8.176)*SEMITONE_CONST
     *     The musical note A = MIDI note 69 (0x45) = 440 Hz.
     * </pre>
     * This call is a non-blocking call. Notice that this method may
     * utilize CPU resources significantly on devices that don't
     * have hardware support for tone generation.
     *
     * @param aNote Defines the tone of the note as specified by the
     * above formula.
     * @param aDuration The duration of the tone in milli-seconds.
     * Duration must be positive.
     * @param aVolume Audio volume range from 0 to 100.  100 represents
     * the maximum
     * volume at the current hardware level.  Setting the volume to a
     * value less
     * than 0 will set the volume to 0.  Setting the volume to greater than
     * 100 will set the volume to 100.
     *
     * @exception IllegalArgumentException Thrown if the given note or
     * duration is out of range.
     * @exception MediaException Thrown if the tone cannot be played
     * due to a device-related problem.
     */
    public void playTone(int aNote, int aDuration, int aVolume)
    throws MediaException
    {
        iPlayToneImpl.playTone(aNote, aDuration, aVolume);
    }

    /**
     * Get the time-base object for the system.
     * @return The system time base.
     */
    public TimeBase getSystemTimeBase()
    {
        return iSystemTimeBase;
    }

    public boolean isValidContentType(String contentType)
    {
        for (int i=0; i < contentType.length(); i++)
        {
            if ((contentType.charAt(i) >= 0 && contentType.charAt(i) <= 31) || contentType.charAt(i) == 127)
                return false;
        }
        return true;
    }



// MMAPI UI 3.x req
    /**
     * get midlet state
     */

    public boolean isForground()
    {
        return iForegroundListener.isForeground();
    }

    private native int _createManager(int aEventSourceHandle,
                                      int aMIDletSuiteID);
    private native int _createEventSource();
    private native void _dispose(int aEventSourceHandle);

    /**
     * Releases native resources.
     * @param aEventSourceHandle Handle to native CMMAEventSource instance.
     */
    private native void _release(int aFunctionSourceHandle);

    private static native String[] _getSupportedContentTypes(int aFunctionSourceHandle,
            int aManagerHandle,
            String aContentType);

    private static native String[] _getSupportedProtocols(int aFunctionSourceHandle,
            int aManagerHandle,
            String aProtocol);
}

// End of File
