/*****************************************************************************
 *
 * $Id$
 *
 * Copyright (C) 2009 - 2012  Richard Hacker <lerich@gmx.net>
 *                            Florian Pose <fp@igh-essen.com>
 *
 * This file is part of the PdCom library.
 *
 * The PdCom library 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 3 of the License, or (at your
 * option) any later version.
 *
 * The PdCom library 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 the PdCom Library. If not, see <http://www.gnu.org/licenses/>.
 *
 ****************************************************************************/

#ifndef PDCOM_DATA_H
#define PDCOM_DATA_H

#include <vector>
#include <memory>
#include "Exception.h"

namespace PdCom {

using std::vector;

/****************************************************************************/

/** Data container.
 *
 * This serves as a generic data container for the Variable class.
 */
class Data {
    friend class Variable;

    public:
        /** Data types.
         */
        enum Type {
            bool_T,
            uint8_T,
            sint8_T,
            uint16_T,
            sint16_T,
            uint32_T,
            sint32_T,
            uint64_T,
            sint64_T,
            single_T,
            double_T,
        };

        const Type type; /**< Data type. */

        /** Type specifier for Dimensions.
         *
         * It is essentially a std::vector.
         */
        class Dimension: public vector<size_t> {
            public:
                /** Constructor with optional allocator.
                 */
                explicit Dimension(
                        const std::allocator<size_t>& a
                        = std::allocator<size_t>() /**< Allocator. */
                        );

                /** Constructor with size.
                 */
                explicit Dimension(
                        size_t n, /**< Number of elements. */
                        const size_t& value = size_t(), /**< Initializer. */
                        const std::allocator<size_t>& a
                        = std::allocator<size_t>() /**< Allocator. */
                        );

                /** Constructor with iterators.
                 */
                Dimension(
                    const vector<size_t>::iterator& first, /**< Start iterator. */
                    const vector<size_t>::iterator& last, /**< End iterator. */
                    const std::allocator<size_t>& a
                    = std::allocator<size_t>() /**< Allocator. */
                    );

                /** Constructor with vector initializer.
                 */
                Dimension(
                        const vector<size_t>& other /**< Vector to copy
                                                      dimensions from. */
                        );

                /** Get number of elements.
                 */
                size_t getElementCount() const;


                /** \return Offset of the element specified by \a element.
                 */
                unsigned int getOffset(
                        const Dimension* element /**< Index. */
                        ) const;

                /** Exception for Zero-Dimension cases.
                 */
                struct ZeroDimensionException: public Exception {
                    /** Constructor.
                     */
                    ZeroDimensionException():
                        Exception("Zero dimension not allowed") {}
                };
        };
        const Dimension dimension; /**< Dimensions. */

        /** \return Storage requirement of data type */
        static size_t getTypeWidth(
                Type type /**< Data type to calculate the width of. */
                );

        /** Allocator for the Data class.
         */
        class Allocator {
            public:
                /** Constructor.
                 */
                Allocator() {};

                /** Destructor.
                 */
                virtual ~Allocator() {};

                /** Allocate \a n objects of type \a t.
                 *
                 * Redefine this in a derived class, if necessary.
                 */
                virtual char* allocate(
                        size_t n, /**< Number of elements. */
                        Type t /**< Type of the elements. */
                        ) {
                    return new char[getTypeWidth(t)*n];
                }

                /** Free the memory.
                 *
                 * Redefine this in a derived class, if necessary.
                 */
                virtual void deallocate(
                        char* p /**< Pointer to allocated memory. */
                        ) {
                    delete[] p;
                }
        };

        /** Constructor.
         */
        Data(
                const Type& t, /**< Data type. */
                const Dimension& dim, /**< Dimensions. */
                const Allocator& a = Allocator() /**< Allocator to use. */
                );
        virtual ~Data();

        /** Get the internal data pointer.
         */
        const char* getDataPtr(
                const Dimension* idx = 0 /**< Index. */
                ) const;

    protected:
        char* dataPtr; /**< Pointer to the data memory. */
        const size_t elementCount; /**< Number of elements. */
        const size_t memSize; /**< Data size in byte. */

        /** Allocates the data memory using the internal allocator.
         */
        void allocateMemory();

    private:
        Allocator allocator; /**< Allocator used by allocateMemory(). */

};

} // namespace

#endif // PDCOM_DATA_H
