/*
* Copyright (c) 2002-2007 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:  This class is used for MIDI.
*
*/


//  INCLUDE FILES
#include <mmf/server/mmffile.h>
#include <logger.h>
#include <e32base.h>
#include <AudioPreference.h>

#include "cmmamidiplayer.h"
#include "mmmadisplay.h"

const TInt KErrorMessageSize = 32;
_LIT(KErrDefaultError, "Symbian OS Error: %d");
const TInt KMidiDefaultBank = 0x3c80;
const TInt KMidiDrumBank = 0x3c7c;
const TInt KMidiDrumChannel = 9;
const TInt KMidiChannels = 16;
const TInt KMidiDefaultInstrument = 1;

CMMAMIDIPlayer* CMMAMIDIPlayer::NewLC(const TDesC& aContentType,
                                      TFileName aFileName)
{
    CMMAMIDIPlayer* self = new(ELeave) CMMAMIDIPlayer(aFileName);
    CleanupStack::PushL(self);
    self->ConstructL(aContentType);
    return self;
}

CMMAMIDIPlayer::~CMMAMIDIPlayer()
{
    CloseClientUtility();
    // new function added to CMMAPlayer delete the controls before the destruction of iMidi.
    DeleteControls();
    delete iMidi;
    delete iActiveSchedulerWait;
    iObservers.Close();

    LOG(EJavaMMAPI, EInfo, "MMA::CMMAMIDIPlayer::~CMMAMIDIPlayer");
}

void CMMAMIDIPlayer::ConstructL(const TDesC& aContentType)
{
    LOG(EJavaMMAPI, EInfo, "CMMAMIDIPlayer::ConstructL");
    iContentType = aContentType.AllocL();
    iActiveSchedulerWait = new(ELeave)CActiveSchedulerWait;
    iMidi = CMidiClientUtility::NewL(*this, KAudioPriorityRecording,
                                     KMMAMIDIPriorityPreference, ETrue);

    CMMAPlayer::ConstructL();
}

CMMAMIDIPlayer::CMMAMIDIPlayer(TFileName aFileName):
        iFileName(aFileName),
        iMediaTime(KTimeUnknown), iStartedEventTime(0)
{
}

EXPORT_C CMidiClientUtility* CMMAMIDIPlayer::MidiClient() const
{
    return iMidi;
}


void CMMAMIDIPlayer::RealizeL()
{
    LOG(EJavaMMAPI, EInfo, "CMMAMIDIPlayer::RealizeL");
    CMMAPlayer::RealizeL();
}

void CMMAMIDIPlayer::PrefetchL()
{
    LOG1(EJavaMMAPI, EInfo, "CMMAMIDIPlayer::PrefetchL stream count %d", iSourceStreams.Count());
    if (iFileName != KNullDesC)
    {
        iMidi->OpenFile(iFileName);
    }
    else if (iSourceStreams.Count() == 0)
    {
        // We have no data, but need to initialize the player
        // Preparing all channels
        for (TInt i = 0; i < KMidiChannels; i++)
        {
            iMidi->SetInstrumentL(i, KMidiDefaultBank,
                                  KMidiDefaultInstrument);
        }

        // Setting drums to channel 10
        iMidi->SetInstrumentL(KMidiDrumChannel, KMidiDrumBank,
                              KMidiDefaultInstrument);

        // Start it immediately in order to get MIDIControl work without
        // calling the start. This is how reference implementation works.
        iMidi->Play();
    }
    else
    {
        // Created with content, audio player will initialize controller
        // and data source.
        iSourceStreams[ 0 ]->ReadAllL();
    }
}

//
// This method is called when CMMASourceStreamReader finished reading
// initiated in PrefetchL
//
void CMMAMIDIPlayer::ReadCompletedL(TInt aStatus, const TDesC8& aData)
{
    if (aStatus < KErrNone)
    {
        PostActionCompleted(aStatus);
    }
    else
    {
        // we're not finished yet
        iMidi->OpenDes(aData);      //wait for MMidiClientUtilityObserver::MmcuoStateChanged
    }
}

void CMMAMIDIPlayer::DeallocateL()
{
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: DeallocateL +");
    if (iState == EPrefetched)
    {
        CloseClientUtility();
        ResetSourceStreams();
        ChangeState(ERealized);
    }
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: DeallocateL -");
}

void CMMAMIDIPlayer::StartL()
{
    iMediaTime = KTimeUnknown;

    // Player is already started if this player is constructed with
    // device://midi locator.
    TBool isDeviceMidi = (iSourceStreams.Count() == 0 &&
                          iFileName == KNullDesC);
    if (!isDeviceMidi)
    {
        iMidi->Play();
    }

    // inform java side
    PostLongEvent(CMMAPlayerEvent::EStarted, iStartedEventTime);
    ChangeState(EStarted);

    // To achieve similar functionality as reference implementation,
    // END_OF_MEDIA must be sent right after Start on device://midi.
    if (isDeviceMidi)
    {
        PostLongEvent(CMMAPlayerEvent::EEndOfMedia, iStartedEventTime);
        ChangeState(EPrefetched);
    }
    PostActionCompletedStart();
    //PostActionCompleted(KErrNone);   // java start return
}

void CMMAMIDIPlayer::StopL(TBool aPostEvent)
{
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer::StopL");
    if (iState == EStarted)
    {
        TInt64 time;
        GetMediaTime(&time);
        iStartedEventTime = time;

        // do the stop only if we are playing some content
        if ((iSourceStreams.Count() > 0) ||
                (iFileName != KNullDesC))
        {
            // should actually pause!!!
            iMidi->Stop(TTimeIntervalMicroSeconds(0));
        }

        if (aPostEvent)
        {
            PostLongEvent(CMMAPlayerEvent::EStopped, time);
        }
        // go back to prefetched state
        ChangeState(EPrefetched);
    }
}

void CMMAMIDIPlayer::GetDuration(TInt64* aDuration)
{
    // Special case for device://midi
    if ((iSourceStreams.Count() == 0) &&
            (iFileName == KNullDesC))
    {
        iDuration = KErrNone;
    }
    else if (iDuration == KTimeUnknown)
    {
        TRAPD(err, iDuration = iMidi->DurationMicroSecondsL().Int64());
        if (err != KErrNone)
        {
            // Duration was not available.
            iDuration = KTimeUnknown;
        }
    }

    *aDuration = iDuration;
}

void CMMAMIDIPlayer::SetMediaTimeL(TInt64* aTime)
{
    TTimeIntervalMicroSeconds position(*aTime);
    iMidi->SetPositionMicroSecondsL(position);

    // Reset cached media time, because actual set position may be
    // something else than aTime.
    iMediaTime = KTimeUnknown;

    // Inform about the position change to the StateListeners
    ChangeState(iState);

    // Get the actual media time
    GetMediaTime(aTime);

    iStartedEventTime = iMediaTime;
}

void CMMAMIDIPlayer::GetMediaTime(TInt64* aMediaTime)
{
    // Special case for device://midi
    if ((iSourceStreams.Count() == 0) &&
            (iFileName == KNullDesC))
    {
        iMediaTime = KErrNone;
    }
    else if (iMediaTime == KTimeUnknown || iState == EStarted)
    {
        TTimeIntervalMicroSeconds position(0);
        TRAPD(error, position = iMidi->PositionMicroSecondsL());

        if (error == KErrNone)
        {
            TInt64 newTime = position.Int64();

            // Sanity check for media time going backwards or beyond the
            // duration.
            // Some native controls may return zero media time for
            // a few moments just before playback will complete.
            if (newTime < iMediaTime ||
                    (iDuration > 0 && newTime > iDuration))
            {
                GetDuration(&iMediaTime);
            }
            else
            {
                // set return value
                iMediaTime = newTime;
            }
        }
        else
        {
            // media time cannot be get
            iMediaTime = KTimeUnknown;
        }
    }
    *aMediaTime = iMediaTime;
}

void CMMAMIDIPlayer::ReInitializeMidiEngineL(const TDesC8* aMidiSequence)
{
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMIDIPlayer: ReInitializeMidiEngineL: + ");

    CloseClientUtility();

    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: ReInitializeMidiEngineL: Opening descriptor");

    iMidi->OpenDes(*aMidiSequence);
    // Wait until asynchronous OpenDes call has completed
    if (!iActiveSchedulerWait->IsStarted())
    {
        iActiveSchedulerWait->Start();
    }
    ChangeState(EPrefetched);
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMIDIPlayer: ReInitializeMidiEngineL: - ");
}

void CMMAMIDIPlayer::addObserverL(MMidiClientUtilityObserver* aObserver)
{
    iObservers.AppendL(aObserver);
}

void CMMAMIDIPlayer::CloseL()
{
    CMMAPlayer::CloseL();
    CloseClientUtility();
}

const TDesC& CMMAMIDIPlayer::Type()
{
    return KMMAMIDIPlayer;
}

void CMMAMIDIPlayer::PlayCompleteL(TInt aError)
{
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: PlayCompleteL +");
    ELOG1(EJavaMMAPI, "MMA: CMMAMidiPlayer: PlayCompleteL: Error=%d", aError);
    TInt64 duration;
    GetDuration(&duration);
    iMediaTime = duration;
    iStartedEventTime = 0;

    iMidi->Stop(TTimeIntervalMicroSeconds(0));

    // go back to prefetched state
    ChangeState(EPrefetched);   // ready to play again

    // Send 'Stopped' only when stop() is called.
    PostLongEvent(CMMAPlayerEvent::EEndOfMedia, duration);

    if (aError == KErrNone)
    {
        iRepeatCount++;

        if (iRepeatForever || iRepeatCount < iRepeatNumberOfTimes)
        {
            StartL();
        }
        else
        {
            iRepeatCount = 0;
        }
    }
    else
    {
        // error has occured, setting correct number of
        // repeats for next start
        SetLoopCount(iRepeatNumberOfTimes);
    }
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: PlayCompleteL -");
}

void CMMAMIDIPlayer::MmcuoStateChanged(TMidiState aOldState,
                                       TMidiState aNewState,
                                       const TTimeIntervalMicroSeconds& aTime,
                                       TInt aError)
{
    TInt err = aError;

    ELOG3(EJavaMMAPI, "MMA: CMMAMIDIPlayer: MmcuoStateChanged: Old=%d, New=%d, Error=%d",
          aOldState,
          aNewState,
          err);
    // Closing the utility or reinitialising
#ifdef RD_JAVA_TMIDISTATECHANGE
    if (iActiveSchedulerWait->IsStarted() &&
            ((aNewState == EMidiStateClosedDisengaged) ||
             (aNewState == EMidiStateOpenDisengaged) ||
             (aNewState == EMidiStateClosedEngaged)))
#else
    if (iActiveSchedulerWait->IsStarted() &&
            ((aNewState == EClosedDisengaged) ||
             (aNewState == EOpenDisengaged) ||
             (aNewState == EClosedEngaged)))
#endif
    {
        iActiveSchedulerWait->AsyncStop();
    }
    // changing from realized to prefetched state
#ifdef RD_JAVA_TMIDISTATECHANGE
    else if ((iState == ERealized) &&
             (aOldState == EMidiStateClosedDisengaged) &&
             ((aNewState == EMidiStateOpenDisengaged) ||
              (aNewState == EMidiStateClosedEngaged) ||
              (aNewState == EMidiStateClosedDisengaged)))
#else
    else if ((iState == ERealized) &&
             (aOldState == EClosed) &&
             ((aNewState == EOpen) || (aNewState == EClosedEngaged) ||
              (aNewState == EClosed)))     // EClosed is EClosedDisengaged
#endif
    {
        if (aError == KErrNone)
        {
            // prefetch succeed
            ChangeState(EPrefetched);
        }
        // else state remains realized

        // inform java
        PostActionCompleted(aError);
        err = KErrNone; // don't report the error again
    }
#ifdef RD_JAVA_TMIDISTATECHANGE
    else if ((aOldState == EMidiStateOpenPlaying) &&
             (aNewState == EMidiStateOpenEngaged) &&
             (iState == EStarted))
#else
    else if ((aOldState == EPlaying) &&
             (aNewState == EOpenEngaged) &&
             (iState == EStarted))
#endif
    {
        // If iState is not EStarted PlayCompleteL may not be called because
        // player may be already stopped.

        // playing completed
        TRAPD(playErr, PlayCompleteL(aError));
        if (playErr != KErrNone)
        {
            err = playErr;
        }
    }

    if (err != KErrNone)
    {
        TBuf<KErrorMessageSize> errorMessage;
        errorMessage.Format(KErrDefaultError, err);
        PostStringEvent(CMMAPlayerEvent::EError, errorMessage);
        if (iActiveSchedulerWait->IsStarted())
        {
            iActiveSchedulerWait->AsyncStop();
        }
    }

    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoStateChanged(aOldState, aNewState, aTime, aError);
    }

    LOG1(EJavaMMAPI, EInfo, "MMA: CMMAMIDIPlayer: MmcuoStateChanged: midi state %d",
         iMidi->State());
}

void CMMAMIDIPlayer::MmcuoTempoChanged(TInt aMicroBeatsPerMinute)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoTempoChanged(aMicroBeatsPerMinute);
    }
}

void CMMAMIDIPlayer::MmcuoVolumeChanged(TInt aChannel,TReal32 aVolumeInDecibels)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoVolumeChanged(aChannel, aVolumeInDecibels);
    }
}

void CMMAMIDIPlayer::MmcuoMuteChanged(TInt aChannel,TBool aMuted)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoMuteChanged(aChannel, aMuted);
    }
}

void CMMAMIDIPlayer::MmcuoSyncUpdate(const TTimeIntervalMicroSeconds& aMicroSeconds,TInt64 aMicroBeats)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoSyncUpdate(aMicroSeconds, aMicroBeats);
    }
}

void CMMAMIDIPlayer::MmcuoMetaDataEntryFound(const TInt aMetaDataEntryId,const TTimeIntervalMicroSeconds& aPosition)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoMetaDataEntryFound(aMetaDataEntryId, aPosition);
    }
}

void CMMAMIDIPlayer::MmcuoMipMessageReceived(const RArray<TMipMessageEntry>& aMessage)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoMipMessageReceived(aMessage);
    }
}

void CMMAMIDIPlayer::MmcuoPolyphonyChanged(TInt aNewPolyphony)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoPolyphonyChanged(aNewPolyphony);
    }
}

void CMMAMIDIPlayer::MmcuoInstrumentChanged(TInt aChannel,TInt aBankId,TInt aInstrumentId)
{
    // notify observers
    TInt count = iObservers.Count();
    for (TInt i = 0; i < count; i++)
    {
        iObservers[ i ]->MmcuoInstrumentChanged(aChannel, aBankId, aInstrumentId);
    }
}

void CMMAMIDIPlayer::CloseClientUtility()
{
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: CloseClientUtility +");
    if (iMidi &&
            iActiveSchedulerWait &&
#ifdef RD_JAVA_TMIDISTATECHANGE
            (iMidi->State() != EMidiStateClosedDisengaged))
#else
            (iMidi->State() != EClosed))
#endif
    {
        // Have to stop midi before closing,
        // this case is for device://midi
#ifdef RD_JAVA_TMIDISTATECHANGE
        if (iMidi->State() == EMidiStateClosedEngaged)
#else
        if (iMidi->State() == EClosedEngaged)
#endif
        {
            iMidi->Stop(TTimeIntervalMicroSeconds(0));
            if (!iActiveSchedulerWait->IsStarted())
            {
                iActiveSchedulerWait->Start();
            }
        }
        else
        {
            // Calling Close and Stop or Stop and Close should
            // always lead to EClosed state.

            // From EOpenEngaged or EOpenPlaying to EClosedEngaged
            // or from EOpenDisengaged to EClosedDisengaged
            iMidi->Close();
            if (!iActiveSchedulerWait->IsStarted())
            {
                iActiveSchedulerWait->Start();
            }

            LOG1(EJavaMMAPI, EInfo, "State after Close: %d", iMidi->State());

            // If not in EClosedDisengaged yet
#ifdef RD_JAVA_TMIDISTATECHANGE
            if (iMidi->State() != EMidiStateClosedDisengaged)
#else
            if (iMidi->State() != EClosed)
#endif
            {
                // From EClosedEngaged to EClosedDisengaged
                iMidi->Stop(TTimeIntervalMicroSeconds(0));
                if (!iActiveSchedulerWait->IsStarted())
                {
                    iActiveSchedulerWait->Start();
                }
                LOG1(EJavaMMAPI, EInfo, "State after Stop: %d", iMidi->State());
            }
        }
        LOG1(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: CloseClientUtility: State after close: %d", iMidi->State());
    }
    LOG(EJavaMMAPI, EInfo, "MMA: CMMAMidiPlayer: CloseClientUtility -");
}

//  END OF FILE
