/*
 * Copyright Staffan Gimåker 2006-2010.
 *
 * ---
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef PEEKABOT_CLIENT_PEEKABOT_CLIENT_HH_INCLUDED
#define PEEKABOT_CLIENT_PEEKABOT_CLIENT_HH_INCLUDED


#include <stdexcept>
#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>

#include "../Visibility.hh"
#include "DelayedDispatch.hh"
#include "OperationResult.hh"
#include "Recording.hh"


namespace peekabot
{
    class Action;

    namespace client
    {
        class ClientImpl;
        class PeekabotProxyBase;

        /**
         * \brief Encapsulates a connection to a peekabot server.
         */
        class PEEKABOT_API PeekabotClient
        {
            friend class PeekabotProxyBase;

        public:
            PeekabotClient();

            /**
             * \brief Shallow copy construction!
             *
             * Since copy-construction is shallow, the connection, etc. are
             * shared by all copies of the client.
             */
            PeekabotClient(const PeekabotClient &client);

            virtual ~PeekabotClient();

            /// \name Blocking methods
            /// @{

            /**
             * \brief Connect the client to a peekabot server.
             *
             * Unlike most other client API methods, this particular method
             * is \e synchronous. It will initiate a connection with the server
             * given, and go through the authentication process before
             * returning.
             *
             * Operations committed prior to connecting to a server are silently
             * discarded.
             *
             * \exception peekabot::client::AlreadyConnected Thrown if the
             * client is already connected.
             * \exception peekabot::client::AuthenticationFailed Thrown when
             * authentication with the server fails (probably beacause the
             * other end is in fact not a peekabot server, or the server and
             * client are incompatible).
             * \exception peekabot::client::HostResolveFailed Thrown when the
             * given hostname cannot be resolved.
             * \exception peekabot::client::ConnectionRefused Thrown when a
             * connection to the host could not be established.
             *
             * \remarks Prior to peekabot 0.6 operations committed before
             * connecting to the server were queued up for transmission when
             * connected.
             */
            void connect(const std::string &hostname, unsigned int port = 5050);

            /**
             * \brief Disconnect the client from the peekabot server.
             *
             * If the client is not connected, this method is a no-op.
             *
             * If there's an active bundle, it will be implicitly ended.
             *
             * Unsent data will be discarded, call flush() or sync() prior to
             * disconnect() to avoid discarding data.
             */
            void disconnect();

            /**
             * \brief Block until all outbound data has been sent.
             *
             * If the connection is terminated during a call to flush, the
             * method will return, even though not all actions were sent.
             */
            void flush();

            /**
             * \brief Block until all prior operations have been sent and
             * executed in the peekabot server.
             *
             * If the connection is terminated during a call to sync, the
             * method will return, even though not all actions were sent or a
             * response received from the server.
             *
             * \remarks sync() will execute immediately, even if there's an
             * active bundle. This behaviour guarantees that the calling
             * application won't hang when syncing inside a bundle.
             *
             * \sa flush()
             */
            void sync();

            /**
             * \brief Record all peekabot operations to the given file. The
             * file can be replayed later using open_recording().
             *
             * \exception std::runtime_error Thrown if recording is already in
             * progress.
             *
             * \sa open_recording(const std::string &), stop_recording(),
             * is_recording()
             */
            void start_recording(const std::string &filename);

            /**
             * \brief Stop recording.
             *
             * \exception std::runtime_error Thrown if recording is not active.
             *
             * \sa start_recording(const std::string &), is_recording()
             */
            void stop_recording();

            /// @}
            /// \name Non-blocking methods
            /// @{

            /**
             * \brief Get the version of the peekabot client library.
             */
            void client_version(int &major, int &minor, int &rev) const;

            /**
             * \brief Get the version of the peekabot client library.
             */
            const std::string &client_version_str() const;

            /**
             * \brief Returns true if the client is connected to a server,
             * false otherwise.
             */
            bool is_connected() const;

            /**
             * \brief Returns true if recording is active, false otherwise.
             *
             * \sa start_recording(const std::string &), stop_recording()
             */
            bool is_recording() const;

            /**
             * \brief Perform a no-op on the peekabot server.
             *
             * This is useful for synchronization purposes:
             * \code
             * peekabot::Status s = client.noop().status();
             * ...
             * s.wait_until_completed();
             * \endcode
             *
             * As illustrated above, this provides, for example, a way to make
             * sure that the data from the last frame was received and
             * processed - this approach, by nature, yields a smaller delay
             * than using sync().
             */
            DelayedDispatch noop();

            /**
             * \brief Begin a bundle.
             *
             * Any operations issued, in the same thread, between a call to
             * begin_bundle() and end_bundle() are bundled up and sent first
             * when end_bundle() is called. Bundles are thread local, that is,
             * a begin_bundle() call in thread A does not affect bundling in
             * thread B, allowing bundling to be safely used in a
             * multi-threaded setup.
             *
             * Bundles are atomic in the sense that all the operations contained
             * in the bundle are guaranteed to be executed between two frames.
             * E.g. you won't see strange effects that a newly added object is
             * drawn once before you have the time to set its scale and pose
             * appropriately.
             *
             * In addition, all operations are guaranteed to execute
             * sequentially "without interruption" - operations from other
             * clients are never interleaved.
             *
             * \sa end_bundle(), is_bundling()
             */
            void begin_bundle();

            /**
             * \brief End a bundle, for the current thread.
             *
             * \throw std::runtime_error Thrown if the client currently isn't
             * bundling operations (is_bundling() == \c false).
             */
            DelayedDispatch end_bundle();

            /**
             * \brief Returns true if and only if the client is currently
             * bundling operations for the current thread.
             */
            bool is_bundling() const;

            Recording open_recording(const std::string &filename);

            /**
             * \brief Upload a client-side file to the peekabot server.
             *
             * After the file has been uploaded, it can be used as any other
             * file. Uploaded client-side files always have precedence over
             * server-side files. Files uploaded by one client cannot be used
             * by another, and any uploaded files are discarded when the client
             * disconnects.
             *
             * \throws std::runtime_error If there is an error reading the
             * file.
             */
            DelayedDispatch upload_file(const std::string &filename);

            /**
             * \brief Remove a file previously uploaded through
             * upload_file(const std::string &).
             */
            DelayedDispatch remove_file(const std::string &filename);

            /// @}

        private:
            PEEKABOT_HIDDEN PeekabotClient(boost::shared_ptr<ClientImpl> impl);

            PEEKABOT_HIDDEN boost::shared_ptr<ClientImpl> get_impl();

            PEEKABOT_HIDDEN const boost::shared_ptr<ClientImpl> get_impl() const;

        private:
            /**
             * \brief The lower level client implementation.
             */
            boost::shared_ptr<ClientImpl> m_impl;
        };
    }
}


#endif // PEEKABOT_CLIENT_PEEKABOT_CLIENT_HH_INCLUDED
