/*
Copyright (C) 2001 Paul Davis
Copyright (C) 2004-2008 Grame

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

*/

#ifndef __JackTransportEngine__
#define __JackTransportEngine__

#include "JackAtomicArrayState.h"
#include "JackCompilerDeps.h"
#include "types.h"

namespace Jack
{

typedef enum {
    TransportCommandNone = 0,
    TransportCommandStart = 1,
    TransportCommandStop = 2,
} transport_command_t;

/*!
\brief The client transport structure.

We have:

	- a "current" position
	- a "pending" position prepared by the server at each cycle
	- a "request" position wanted by a client

	At the beginning of a cycle the server needs to select a new current position. When a request and a pending position are available,
	the request takes precedence on the pending one. The server atomically switches to the new position.
	The current position can be read by clients.

	We use a JackAtomicArrayState pattern that allows to manage several "next" states independently.

    In jack1 implementation, transport code (jack_transport_cycle_end) was not called if the graph could not be locked (see jack_run_one_cycle).
    Here transport cycle (CycleBegin, CycleEnd) has to run in the RT thread concurrently with code executed from the "command" thread.

    Each client maintains a state in it's shared memory area defined by:

    - it's current transport state
    - a boolean that is "true" when slow-sync cb has to be called
    - a boolean that is "true" when timebase cb is called with new_pos on

    Several operations set the "slow-sync cb" flag to true:

        - setting a new cb (client)
        - activate (client)
        - transport start (server)
        - new pos (server)

    Slow-sync cb calls stops when:

        - the cb return true (client)
        - deactivate (client)
        - transport stop (server)

    Several operations set the "timebase cb" flag to true:

        - setting a new cb (client)
        - activate (client)
        - transport start (server) ??
        - new pos (server)

    Timebase cb "new_pos" argument calls stops when:

        - after one cb call with "new_pos" argument true (client)
        - deactivate (client)
        - release (client)
        - transport stop (server)

*/

class JackClientInterface;

PRE_PACKED_STRUCTURE
class SERVER_EXPORT JackTransportEngine : public JackAtomicArrayState<jack_position_t>
{

    private:

        jack_transport_state_t fTransportState;
        volatile transport_command_t fTransportCmd;
        transport_command_t fPreviousCmd;		/* previous transport_cmd */
        jack_time_t fSyncTimeout;
        int fSyncTimeLeft;
        int fTimeBaseMaster;
        bool fPendingPos;
        bool fNetworkSync;
        bool fConditionnal;
        alignas(SInt32) SInt32 fWriteCounter;

        bool CheckAllRolling(JackClientInterface** table);
        void MakeAllStartingLocating(JackClientInterface** table);
        void MakeAllStopping(JackClientInterface** table);
        void MakeAllLocating(JackClientInterface** table);

        void SyncTimeout(jack_nframes_t frame_rate, jack_nframes_t buffer_size);

    public:

        JackTransportEngine();

        ~JackTransportEngine()
        {}

        void SetCommand(transport_command_t state)
        {
            fTransportCmd = state;
        }

        jack_transport_state_t GetState() const
        {
            return fTransportState;
        }

        void SetState(jack_transport_state_t state)
        {
            fTransportState = state;
        }

        /*
        	\brief
        */
        int ResetTimebase(int refnum);

        /*
        	\brief
        */
        int SetTimebaseMaster(int refnum, bool conditionnal);

        void GetTimebaseMaster(int& refnum, bool& conditionnal)
        {
            refnum = fTimeBaseMaster;
            conditionnal = fConditionnal;
        }

        /*
        	\brief
        */
        void CycleBegin(jack_nframes_t frame_rate, jack_time_t time);

        /*
        	\brief
        */
        void CycleEnd(JackClientInterface** table, jack_nframes_t frame_rate, jack_nframes_t buffer_size);

        /*
        	\brief
        */
        void SetSyncTimeout(jack_time_t timeout)
        {
            fSyncTimeout = timeout;
        }

        void ReadCurrentPos(jack_position_t* pos);

        jack_unique_t GenerateUniqueID()
        {
            return (jack_unique_t)INC_ATOMIC(&fWriteCounter);
        }

        void RequestNewPos(jack_position_t* pos);

        jack_transport_state_t Query(jack_position_t* pos);

        jack_nframes_t GetCurrentFrame();

        static void CopyPosition(jack_position_t* from, jack_position_t* to);

        bool GetNetworkSync() const
        {
            return fNetworkSync;
        }

        void SetNetworkSync(bool sync)
        {
            fNetworkSync = sync;
        }

} POST_PACKED_STRUCTURE;

} // end of namespace

#endif
