/*
 * Copyright (c) 2010 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:
 * The file provides the implementation of the client side session
 * to the STS Server.
 */

#include "rstssession.h"
#include "stsclientservercommon.h"

const TUint KNumSlots = 30;

/*static*/TInt RStsSession::CallBackThreadMain(TAny* aSession)
    {
    TInt err = KErrNoMemory;

    RThread myThread;
    myThread.SetPriority(EPriorityAbsoluteHigh);
    myThread.Close();

    CTrapCleanup* cleanup = CTrapCleanup::New();

    if (cleanup)
        {
        // Run the server and request a thread rendezvous.
        TRAP( err, ((RStsSession*)aSession)->RunThreadL() );
        delete cleanup;
        }

    return err;
    }

void RStsSession::RunThreadL()
    {
    iState = ERunning;
    // Initialisation complete, now signal the client, if requested.
    RThread::Rendezvous(KErrNone);
    TRequestStatus queueStatus = KRequestPending;
    iMsgQueue.NotifyDataAvailable(queueStatus);

    RThread server;
    TInt err = server.Open(iServerThreadId);
    TRequestStatus serverStatus = KRequestPending;
    server.Logon(serverStatus);

    while (iState == ERunning)
        {
        TStsCallBack message;
        // Using ReceiveBlocking here would block forever if the executive thread
        // dies, so instead wait for either a data available notification or a
        // notification that the executive thread has died.
        User::WaitForRequest(queueStatus, serverStatus);

        if (queueStatus != KRequestPending)
            {
            TInt err = iMsgQueue.Receive(message);
            if (err == KErrNone)
                {
                HandleMessage(message);
                }
            else
                {
                //TODO:Log a message
                }
            queueStatus = KRequestPending;
            iMsgQueue.NotifyDataAvailable(queueStatus);
            }
        if (serverStatus != KRequestPending && iState == ERunning)
            {
            //TODO: Log a message
            //Restart the server
            SignalObservers();
            server.Close();
            CreateServerSession();
            TInt err = server.Open(iServerThreadId);
            TRequestStatus serverStatus = KRequestPending;
            server.Logon(serverStatus);
            }
        }

    iMsgQueue.CancelDataAvailable();
    server.LogonCancel(serverStatus);
    server.Close();
    }

void RStsSession::HandleMessage(TStsCallBack& aMessage)
    {
    TStsCallBackType type = aMessage.callBackType;
    if (type == EStsPlayAlarmComplete)
        {
        MStsPlayAlarmObserver* observer = aMessage.observer;
        unsigned int context = aMessage.alarmContext;
        iObserverMutex.Wait();
        if (observer == iObserverMap[context])
            {
            observer->PlayAlarmComplete(aMessage.alarmContext);
            }
        else
            {
            //TODO: Log a message
            }
        iObserverMap.erase(context);
        iObserverMutex.Signal();
        }
    else if (type == EStsShutdown)
        {
        iState = EStopping;
        }
    else
        {
        //TODO: Log error message
        }
    }

TInt RStsSession::StartServer()
    {
    TInt err = KErrNone;

    // Launch the server executable (i.e. in it its own process).

    // Create a new server process. Simultaneous launching of two such processes 
    // should be detected when the second one attempts to create the server 
    // object, failing with KErrAlreadyExists.
    RProcess server;
    err = server.Create(KStsServerFile, KNullDesC);

    if (err == KErrNone)
        {
        TRequestStatus rendezvousStatus;
        server.Rendezvous(rendezvousStatus);
        server.Resume();

        // wait for start or death
        User::WaitForRequest(rendezvousStatus);

        // we can't use the 'exit reason' if the server panicked as this
        // is the panic 'reason' and may be '0' which cannot be distinguished
        // from KErrNone  
        if (server.ExitType() == EExitPanic)
            {
            err = KErrGeneral;
            }
        else
            {
            err = rendezvousStatus.Int();
            }
        }
    server.Close();

    return err;
    }

TInt RStsSession::StartThread()
    {
    TInt result = iThread.Create(KNullDesC, RStsSession::CallBackThreadMain,
            KDefaultStackSize, &User::Heap(), (TAny*) this);

    if (result == KErrNone)
        {
        TRequestStatus rendezvousStatus = KRequestPending;

        //  Register for rendezvous notification when thread is started.
        iThread.Rendezvous(rendezvousStatus);

        // Start the thread execution
        iThread.Resume();

        // Wait for thread to start.
        User::WaitForRequest(rendezvousStatus);

        result = rendezvousStatus.Int();

        if (result != KErrNone)
            {
            iThread.Kill(result);
            }
        }

    return result;
    }

TInt RStsSession::CreateServerSession()
    {
    // Try to create a session with the server
    TInt result = CreateSession(KStsServerName, TVersion(
            KStsServerMajorVersion, KStsServerMinorVersion, KStsServerBuild),
            KNumSlots, EIpcSession_Sharable);

    // If the server wasn't found, start the server and try creating a session again
    if (result == KErrNotFound || result == KErrServerTerminated)
        {
        result = StartServer();
        if (result == KErrNone || result == KErrAlreadyExists)
            {
            result = CreateSession(KStsServerName, TVersion(
                    KStsServerMajorVersion, KStsServerMinorVersion,
                    KStsServerBuild), KNumSlots, EIpcSession_Sharable);
            }
        }

    if (result == KErrNone)
        {
        TPckg<TThreadId> idPckg(iServerThreadId);
        result = SendReceive(StsMsg_RegisterMsgQueue, TIpcArgs(iMsgQueue,
                &idPckg));
        }

    return result;
    }

TInt RStsSession::Connect()
    {
    iState = EInitializing;

    // Create a nameless global message queue, then pass the handle to the queue to the server.
    TInt result = iMsgQueue.CreateGlobal(KNullDesC, 30);

    // Create thread for receiving asynch callbacks from the server
    if (result == KErrNone)
        {
        result = CreateServerSession();
        if (result == KErrNone)
            {
            result = StartThread();
            if (result == KErrNone)
                {
                result = iObserverMutex.CreateLocal();
                }
            }
        }

    return result;
    }

void RStsSession::Close()
    {
    TRequestStatus logonStatus = KRequestPending;
    iThread.Logon(logonStatus);
    RSessionBase::Close();
    User::WaitForRequest(logonStatus);
    iThread.Close();
    iMsgQueue.Close();
    CleanUpObservers();
    iObserverMutex.Close();
    }

void RStsSession::SendPlayTone(CSystemToneService::TToneType aTone)
    {
    TInt err = SendReceive(StsMsg_PlayTone, TIpcArgs(aTone));
    if (err != KErrNone)
        {
        //TODO: Log a message
        }
    }

void RStsSession::SendPlayAlarm(CSystemToneService::TAlarmType aAlarm,
        unsigned int& aAlarmContext, MStsPlayAlarmObserver& aObserver)
    {
    TPckg<unsigned int> alarmContextPckg(aAlarmContext);
    TInt err = SendReceive(StsMsg_PlayAlarm, TIpcArgs(aAlarm,
            &alarmContextPckg, &aObserver));
    if (err != KErrNone)
        {
        //TODO: Log a message
        aObserver.PlayAlarmComplete(aAlarmContext);
        }
    else
        {
        iObserverMutex.Wait();
        iObserverMap[aAlarmContext] = &aObserver;
        iObserverMutex.Signal();
        }
    }

void RStsSession::SendStopAlarm(unsigned int aAlarmContext)
    {
    iObserverMutex.Wait();
    iObserverMap.erase(aAlarmContext);
    iObserverMutex.Signal();
    TInt err = SendReceive(StsMsg_StopAlarm, TIpcArgs(aAlarmContext));
    if (err != KErrNone)
        {
        //TODO: Log a message
        }
    }

void RStsSession::CleanUpObservers()
    {
    iObserverMutex.Wait();
    while (!iObserverMap.empty())
        {
        //TODO: Add trace here
        unsigned int context = iObserverMap.begin()->first;
        iObserverMap.erase(context);
        }
    iObserverMutex.Signal();
    }

void RStsSession::SignalObservers()
    {
    iObserverMutex.Wait();
    while (!iObserverMap.empty())
        {
        //TODO: Add trace here
        unsigned int context = iObserverMap.begin()->first;
        iObserverMap[context]->PlayAlarmComplete(context);
        iObserverMap.erase(context);
        }
    iObserverMutex.Signal();
    }
