//----------------------------------------------------------------------------
//
// TSDuck - The MPEG Transport Stream Toolkit
// Copyright (c) 2005-2020, Thierry Lelegard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
//----------------------------------------------------------------------------
//!
//!  @file
//!  Serialization of TLV messages.
//!
//----------------------------------------------------------------------------

#pragma once
#include "tstlv.h"
#include "tsByteBlock.h"

namespace ts {
    namespace tlv {
        //!
        //! Serialization of TLV messages.
        //! @ingroup tlv
        //!
        //! A DVB message is serialized in TLV into a ByteBlock.
        //! A Serializer is always associated to a ByteBlock.
        //!
        class TSDUCKDLL Serializer
        {
        private:
            // Private members:
            ByteBlockPtr _bb;    // Associated binary block
            int _length_offset;  // Location of TLV "length" field

        public:
            //!
            //! Constructor.
            //! Associates an existing message block.
            //! @param [in] bb Safe pointer to an existing message block.
            //! The messages will be serialized in this block.
            //!
            Serializer(const ByteBlockPtr& bb) :
                _bb(bb),
                _length_offset(-1)
            {
            }

            //!
            //! Constructor.
            //! Use the same message block as another Serializer.
            //! Useful to nest serializer when building compound TLV parameters.
            //! @param [in] s Another serializer, will use the same byte block for serialization.
            //!
            Serializer(const Serializer& s) :
                _bb(s._bb),
                _length_offset(-1)
            {
            }

            //!
            //! Destructor.
            //! Close potential pending TLV.
            //!
            ~Serializer()
            {
                if (_length_offset >= 0) {
                    closeTLV();
                }
            }

        private:
            // Unreachable constructors and operators.
            Serializer() = delete;
            Serializer& operator=(const Serializer&) = delete;

        public:
            //!
            //! Open a TLV structure.
            //! The tag field and a placeholder for the length field are inserted.
            //! Cannot be nested in the same serializer.
            //! Use nested factories but not nested TLV into one serializer.
            //! @param [in] tag Message or parameter tag.
            //!
            void openTLV(TAG tag);

            //!
            //! Close a TLV structure.
            //! The length field is updated.
            //!
            void closeTLV();

            //!
            //! Insert an unsigned 8-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt8(uint8_t i) {_bb->appendUInt8 (i);}

            //!
            //! Insert an unsigned 16-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt16(uint16_t i) {_bb->appendUInt16(i);}

            //!
            //! Insert an unsigned 16-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt32(uint32_t i) {_bb->appendUInt32(i);}

            //!
            //! Insert an unsigned 64-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt64(uint64_t i) {_bb->appendUInt64(i);}

            //!
            //! Insert a signed 8-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putInt8(int8_t i) {_bb->appendInt8 (i);}

            //!
            //! Insert a signed 16-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putInt16(int16_t i) {_bb->appendInt16(i);}

            //!
            //! Insert a signed 32-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putInt32(int32_t i) {_bb->appendInt32(i);}

            //!
            //! Insert a signed 64-bit integer value in the stream.
            //! @param [in] i Integer value to insert.
            //!
            void putInt64(int64_t i) {_bb->appendInt64(i);}

            //!
            //! Insert a TLV field containing an unsigned 8-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt8(TAG tag, uint8_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(1); _bb->appendUInt8 (i);}

            //!
            //! Insert a TLV field containing an unsigned 16-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt16(TAG tag, uint16_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(2); _bb->appendUInt16(i);}

            //!
            //! Insert a TLV field containing an unsigned 32-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt32(TAG tag, uint32_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(4); _bb->appendUInt32(i);}

            //!
            //! Insert a TLV field containing an unsigned 64-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putUInt64(TAG tag, uint64_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(8); _bb->appendUInt64(i);}

            //!
            //! Insert a TLV field containing a signed 8-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putInt8(TAG tag, int8_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(1); _bb->appendInt8 (i);}

            //!
            //! Insert a TLV field containing a signed 16-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putInt16(TAG tag, int16_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(2); _bb->appendInt16(i);}

            //!
            //! Insert a TLV field containing a signed 32-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putInt32(TAG tag, int32_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(4); _bb->appendInt32(i);}

            //!
            //! Insert a TLV field containing a signed 64-bit integer value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            void putInt64(TAG tag, int64_t i) {_bb->appendUInt16(tag); _bb->appendUInt16(8); _bb->appendInt64(i);}

            //!
            //! Insert a TLV field containing a vector of unsigned 8-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putUInt8(TAG tag, const std::vector<uint8_t>& v);

            //!
            //! Insert a TLV field containing a vector of unsigned 16-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putUInt16(TAG tag, const std::vector<uint16_t>& v);

            //!
            //! Insert a TLV field containing a vector of unsigned 32-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putUInt32(TAG tag, const std::vector<uint32_t>& v);

            //!
            //! Insert a TLV field containing a vector of unsigned 64-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putUInt64(TAG tag, const std::vector<uint64_t>& v);

            //!
            //! Insert a TLV field containing a vector of signed 8-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putInt8(TAG tag, const std::vector<int8_t>& v);

            //!
            //! Insert a TLV field containing a vector of signed 16-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putInt16(TAG tag, const std::vector<int16_t>& v);

            //!
            //! Insert a TLV field containing a vector of signed 32-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putInt32(TAG tag, const std::vector<int32_t>& v);

            //!
            //! Insert a TLV field containing a vector of signed 64-bit integer values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] v Vector of integer values to insert.
            //!
            void putInt64(TAG tag, const std::vector<int64_t>& v);

            //!
            //! Insert a boolean value in the stream.
            //! @param [in] val Boolean value to insert.
            //!
            void putBool(bool val) {putUInt8(val ? 1 : 0);}

            //!
            //! Insert a TLV field containing a boolean value in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] val Boolean value to insert.
            //!
            void putBool(TAG tag, bool val) {putUInt8(tag, val ? 1 : 0);}

            //!
            //! Insert a TLV field containing a vector of boolean values in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] val Vector of boolean values to insert.
            //!
            void putBool(TAG tag, const std::vector<bool>& val);

            //!
            //! Insert an integer value in the stream (template variant).
            //! @tparam INT Integer type.
            //! @param [in] i Integer value to insert.
            //!
            template <typename INT, typename std::enable_if<std::is_integral<INT>::value>::type* = nullptr>
            void put(INT i) {_bb->append<INT>(i);}

            //!
            //! Insert a TLV field containing an integer value in the stream (template variant).
            //! @tparam INT Integer type.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] i Integer value to insert.
            //!
            template <typename INT, typename std::enable_if<std::is_integral<INT>::value>::type* = nullptr>
            void put(TAG tag, INT i) {_bb->appendUInt16(tag); _bb->appendUInt16(sizeof(INT)); _bb->append<INT>(i);}

            //!
            //! Insert a TLV field containing a vector of integer values in the stream (template variant).
            //! @tparam INT Integer type.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] val Vector of integer values to insert.
            //!
            template <typename INT, typename std::enable_if<std::is_integral<INT>::value>::type* = nullptr>
            void put(TAG tag, const std::vector<INT>& val)
            {
                for (typename std::vector<INT>::const_iterator it = val.begin(); it != val.end(); ++it) {
                    put<INT>(tag, *it);
                }
            }

            //!
            //! Insert a string in the stream.
            //! @param [in] val String to insert.
            //!
            void put(const std::string& val)
            {
                put(val.data(), val.size());
            }

            //!
            //! Insert a TLV field containing a string in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] val String to insert.
            //!
            void put(TAG tag, const std::string& val)
            {
                _bb->appendUInt16(tag);
                _bb->appendUInt16(uint16_t(val.size()));
                _bb->append(val.data(), val.size());
            }

            //!
            //! Insert a TLV field containing a vector of strings in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] val Vector of strings to insert.
            //!
            void put(TAG tag, const std::vector<std::string>& val);

            //!
            //! Insert a byte block in the stream.
            //! @param [in] bl Byte block to insert.
            //!
            void put(const ByteBlock& bl)
            {
                _bb->append(bl.data(), bl.size());
            }

            //!
            //! Insert a TLV field containing a byte block in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] bl Byte block to insert.
            //!
            void put(TAG tag, const ByteBlock& bl)
            {
                _bb->appendUInt16(tag);
                _bb->appendUInt16(uint16_t(bl.size()));
                _bb->append(bl.data(), bl.size());
            }

            //!
            //! Insert raw data in the stream.
            //! @param [in] pval Address of data to insert.
            //! @param [in] len Length of data to insert.
            //!
            void put(const void *pval, size_t len)
            {
                _bb->append(pval, len);
            }

            //!
            //! Insert a TLV field containing raw data in the stream.
            //! @param [in] tag Message or parameter tag.
            //! @param [in] pval Address of data to insert.
            //! @param [in] len Length of data to insert.
            //!
            void put(TAG tag, const void *pval, size_t len)
            {
                _bb->appendUInt16(tag);
                _bb->appendUInt16(uint16_t(len));
                _bb->append(pval, len);
            }

            //!
            //! Convert to a string (for debug purpose).
            //! @return A string representing the internal state of the Serializer.
            //!
            UString toString() const;
        };

        // Template specializations for performance.
        //! @cond nodoxygen
        template<> inline void Serializer::put(bool val) {putBool(val);}
        template<> inline void Serializer::put(TAG tag, bool val) {putBool(tag, val);}
        template<> inline void Serializer::put(TAG tag, const std::vector<bool>& val) {putBool(tag, val);}
        //! @endcond
    }
}

//!
//! Output operator for ts::tlv::Serializer (for debug purpose).
//! @param [in,out] strm Output stream (text mode).
//! @param [in] ser Serializer to dump.
//! @return A reference to @a strm.
//!
TSDUCKDLL inline std::ostream& operator<<(std::ostream& strm, const ts::tlv::Serializer& ser)
{
    return strm << ser.toString();
}
