/*
 * Copyright Staffan Gimåker 2008-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_OBJECT_PROXY_HH_INCLUDED
#define PEEKABOT_CLIENT_OBJECT_PROXY_HH_INCLUDED


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

#include "PeekabotProxyBase.hh"
#include "../DelayedDispatch.hh"
#include "../Result.hh"
#include "../../Types.hh"
#include "../../Deprecated.hh"


namespace peekabot
{
    class Action;

    namespace client
    {
        class PeekabotClient;
        class ClientImpl;
        class Status;

        /**
         * \brief Base class for all client object proxies.
         *
         * Provides functionality needed by all proxies referring to objects -
         * handling of pseudonyms, and all basic operations pertaining to
         * objects.
         */
        class PEEKABOT_API ObjectProxyBase : public PeekabotProxyBase
        {
        public:
            ObjectProxyBase();

            ObjectProxyBase(const ObjectProxyBase &p);

            virtual ~ObjectProxyBase();


            /**
             * \brief Equality comparison operation.
             *
             * Returns true if the two operands refer to the same object. More
             * specifically, it returns true if they're tied to the same client
             * and are assigned to the same object in the scene.
             *
             * Two proxies with different clients but assigned to the same
             * object are \emph not considered to be equal.
             */
            bool operator==(const ObjectProxyBase &p) const;

            /**
             * \brief Inequality comparison operator.
             *
             * \see operator==
             */
            bool operator!=(const ObjectProxyBase &p) const;

            /**
             * \brief Set the object's transformation matrix.
             *
             * \pre (x1,x2,x3), (y1,y2,y3) and (z1,z2,z3) must all form unit
             * vectors. p1,p2,p3 must all have finite values.
             *
             * \param coord_sys The coordinate system the transformation matrix
             * is specified in.
             */
            DelayedDispatch set_transformation(
                float x1, float y1, float z1, float p1,
                float x2, float y2, float z2, float p2,
                float x3, float y3, float z3, float p3,
                CoordinateSystem coord_sys = PARENT_COORDINATES);

            /**
             * \brief Set the object's transformation matrix.
             *
             * Set the object's transformation from a 4x4 array with either row
             * or column major storage.
             *
             * \param row_major Storage layout of the input \a m.
             * \param coord_sys The coordinate system the transformation matrix
             * is specified in.
             */
            DelayedDispatch set_transformation(
                const float *m, bool row_major = true,
                CoordinateSystem coord_sys = PARENT_COORDINATES);

            /**
             * \brief Set the object's position.
             *
             * \sa translate()
             */
            DelayedDispatch set_position(
                float x, float y, float z,
                CoordinateSystem coord_sys = PARENT_COORDINATES);

            /**
             * \brief Move the object.
             *
             * Translate the object the specified distances along the given
             * coordinate systems axes.
             *
             * \remark translate() in local coordinates is the equivalent
             * to set_position() with local coordinates.
             *
             * \sa set_position()
             */
            DelayedDispatch translate(
                float x, float y, float z,
                CoordinateSystem coord_sys = LOCAL_COORDINATES);

            /**
             * \brief Set the object's orientation.
             *
             * Sets the object's X-axis (in the coordinate system given by
             * \a coord_sys) to \f$(vx,vy,vz)\f$.
             *
             * Note that when the requested orientation equals the negated
             * current orientation (e.g. current is \f$(1, 0, 0)\f$ and
             * requested is \f$(-1, 0, 0)\f$) the change in orientation is
             * ambiguous: we can achieve the desired orientation by rotating
             * 180 degrees around either object's Z or Y axis. In this case,
             * this operation always chooses to rotate around the Z axis.
             */
            DelayedDispatch set_orientation(
                float vx, float vy, float vz,
                CoordinateSystem coord_sys = PARENT_COORDINATES);

            /**
             * \brief Set the object's rotation from Euler angles.
             *
             * The yaw rotation is applied first, then pitch and roll last.
             *
             * \param yaw Rotation about the Z-axis.
             * \param pitch Rotation about the Y-axis.
             * \param roll Rotation about the X-axis.
             *
             * \sa rotate()
             */
            DelayedDispatch set_rotation(
                float yaw, float pitch = 0, float roll = 0,
                CoordinateSystem coord_sys = PARENT_COORDINATES);

            /**
             * \brief Rotate the object around the given rotational axis
             * and pivot.
             *
             * \pre The rotational axis must be a non-zero vector.
             *
             * \param axis_coord_sys The coordinate system in which the
             * rotational axis is specified.
             * \param pivot_coord_sys The coordinate system in which the
             * rotational pivot is specified.
             *
             * \throw std::logic_error Thrown if the specified axis forms a
             * zero vector.
             *
             * \sa set_rotation()
             */
            DelayedDispatch rotate(
                float rad,
                float axis_x, float axis_y, float axis_z,
                float pivot_x = 0, float pivot_y = 0, float pivot_z = 0,
                CoordinateSystem axis_coord_sys = LOCAL_COORDINATES,
                CoordinateSystem pivot_coord_sys = LOCAL_COORDINATES);

            /**
             * \brief Set the object's pose.
             *
             * The yaw rotation is applied first, then pitch and roll last.
             *
             * \param yaw Rotation about the Z-axis.
             * \param roll Rotation about the Y-axis.
             * \param pitch Rotation about the X-axis.
             */
            DelayedDispatch set_pose(
                float x, float y, float z,
                float yaw, float pitch = 0, float roll = 0,
                CoordinateSystem coord_sys = PARENT_COORDINATES);

            /**
             * \brief Set the object's opacity.
             *
             * \param opacity The (absolute or relative) alpha value of the
             * object.
             * \param absolute If set to false, the actual opacity of the object
             * will be computed relative to the parent object.
             *
             * \sa show(), hide()
             *
             * \todo Add a pointer to text explaining how opacity is calculated,
             * and the difference in absolute and relative opacity.
             */
            DelayedDispatch set_opacity(float opacity, bool absolute = false);

            /**
             * \brief Hide/show the object and all it's children.
             *
             * \param visible Set to \c false and \c true to show and hide the
             * object respectively.
             */
            DelayedDispatch set_visibility(bool visible);

            /**
             * \brief Hide the object.
             *
             * \sa set_opacity(), show()
             */
            inline DelayedDispatch hide() { return set_visibility(false); }

            /**
             * \brief Show (unhide) the object.
             *
             * \sa set_opacity(), hide()
             */
            inline DelayedDispatch show() { return set_visibility(true); }

            /**
             * \brief Move the object, and optionally, all its children, to
             * the given layer.
             *
             * \pre \a layer is in range [1,16].
             *
             * \param layer The layer which to move the object/subtree to.
             * \param recursive If \c true, the layer setting is propagated to
             * all of the object's children.
             *
             * \throw std::range_error Thrown when \a layer is outside the
             * allowed range.
             */
            DelayedDispatch set_layer(
                unsigned int layer, bool recursive = false);

            /**
             * \brief Set the object's color.
             *
             * \pre r,g,b are in the range [0,1].
             *
             * \param recursive If \c true, the layer setting is propagated to
             * all of the object's children.
             */
            DelayedDispatch set_color(
                float r, float g, float b, bool recursive = false);

            /**
             * \brief Set the object's name.
             *
             * \warning Remember that object names have to be unique among
             * its siblings.
             *
             * \param name The object's new name.
             */
            DelayedDispatch set_name(const std::string &name);

            /**
             * \brief Remove the object, and all of its children, from the
             * scene.
             */
            DelayedDispatch remove();

            /**
             * \brief Remove all child objects.
             */
            DelayedDispatch clear();

            /**
             * \brief Relocate the object to a new parent.
             *
             * \param retain_world_pose If \c true, the object's absolute pose
             * will remain unchanged, otherwise the aboslute pose might change
             * but the pose relative the parent object will be left unchanged.
             */
            DelayedDispatch rearrange(
                const ObjectProxyBase &new_parent,
                bool retain_world_pose = false,
                NameConflictPolicy conflict_policy = AUTO_ENUMERATE_ON_CONFLICT)
                const;

            /**
             * \brief Load a scene file.
             *
             * The contents of the scene file will be attached underneath this
             * object.
             *
             * \param filename The filename (path) of the \emph server-side
             * scene file to load.
             */
            DelayedDispatch load_scene(
                const std::string &filename,
                NameConflictPolicy conflict_policy = FAIL_ON_CONFLICT);

            /**
             * \brief Get the transformation of the object.
             *
             * The result will be returned in the given coordinate system,
             * either relative the parent object or in world coordinates.
             *
             * \param coord_sys The coordinate system the result should be
             * described in. Must be \c PARENT_COORDINATES or
             * \c WORLD_COORDINATES.
             */
            Result<Transformation> get_transformation(
                CoordinateSystem coord_sys = PARENT_COORDINATES) const;

            /**
             * \brief Get the position of the object.
             *
             * The result will be returned in the given coordinate system,
             * either relative the parent object or in world coordinates.
             *
             * \param coord_sys The coordinate system the result should be
             * described in. Must be \c PARENT_COORDINATES or
             * \c WORLD_COORDINATES.
             */
            Result<Vector3> get_position(
                CoordinateSystem coord_sys = PARENT_COORDINATES) const;

            /**
             * \brief Get the orientation of the object.
             *
             * The result will be returned in the given coordinate system,
             * either relative the parent object or in world coordinates.
             *
             * \param coord_sys The coordinate system the result should be
             * described in. Must be \c PARENT_COORDINATES or
             * \c WORLD_COORDINATES.
             */
            Result<Vector3> get_orientation(
                CoordinateSystem coord_sys = PARENT_COORDINATES) const;

            /**
             * \brief Get the names of all the object's children.
             *
             * The order of the returned names is undefined.
             *
             * \warning
             * Note that by the time the time the client receives the result,
             * children might have been renamed, moved or removed.
             */
            Result<std::vector<std::string> > get_children() const;


        protected:
            /**
             * \brief Returns the pseudonym ID of the object associated with
             * the proxy.
             *
             * \throw std::logic_error Thrown if the proxy is unassigned, i.e.
             * not yet associated with an object.
             */
            PEEKABOT_HIDDEN ObjectID get_object_id()
                const;

            /**
             * \brief Returns the pseudonym ID of the object associated with
             * the given proxy \a p.
             *
             * \sa get_object_id()
             */
            static ObjectID get_object_id(const ObjectProxyBase &p)
               ;

            /**
             * \brief Low level assign - set the pseudonym ID and client of
             * the proxy. No type checking is done.
             */
            PEEKABOT_HIDDEN void unchecked_assign(
                boost::shared_ptr<ClientImpl> client,
                boost::shared_ptr<ObjectID> id);

            /**
             * \brief Low level assign - set the pseudonym ID and client of
             * the proxy to that of \a other. No type checking is done.
             */
            PEEKABOT_HIDDEN void unchecked_assign(const ObjectProxyBase &other);

            PEEKABOT_HIDDEN boost::shared_ptr<ObjectID> get_pseudonym()
                const;

            static boost::shared_ptr<ObjectID> get_pseudonym(
                const ObjectProxyBase &p);

            PEEKABOT_HIDDEN boost::shared_ptr<ObjectID> allocate_pseudonym();

        private:
            mutable boost::shared_ptr<ObjectID> m_pseudonym;
        };


        /**
         * \brief Proxy class used to manipulate any type of (remote) objects
         * in the peekabot server.
         */
        class PEEKABOT_API ObjectProxy : public ObjectProxyBase
        {
        public:
            ObjectProxy();

            ObjectProxy(const ObjectProxyBase &p);

            ObjectProxy &operator=(const ObjectProxy &p);

            ObjectProxy &operator=(const ObjectProxyBase &p);

            /**
             * \brief Assign the proxy to the object with the given path.
             *
             * \param client The client to use for this proxy.
             * \param path The path of the object in the peeakbot scene.
             */
            DelayedDispatch assign(
                PeekabotClient &client,
                const std::string &path);

            /**
             * \brief Assign the proxy to the object with the given parent and
             * name.
             *
             * \param parent The parent of the object referred to by \a name.
             * \param rel_path The path of the object, relative the parent
             * object, to assign this proxy to.
             */
            DelayedDispatch assign(
                const ObjectProxyBase &parent,
                const std::string &rel_path);
        };
    }
}


#endif // PEEKABOT_CLIENT_OBJECT_PROXY_HH_INCLUDED
