/* mbed Microcontroller Library
 * Copyright (c) 2018 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef NFC_NDEF_MESSAGEBUILDER_H_
#define NFC_NDEF_MESSAGEBUILDER_H_

#include <stdint.h>

#include "platform/Span.h"

#include "nfc/ndef/Record.h"

namespace mbed {
namespace nfc {
namespace ndef {

/**
 * @addtogroup nfc
 * @{
 */

/**
 * Construct a NDEF Message.
 */
class MessageBuilder {

public:
    /**
     * Build a record payload.
     */
    struct PayloadBuilder {
        /**
         * Return the size of the payload built by this object.
         *
         * @return The size of the payload.
         */
        virtual size_t size() const = 0;

        /**
         * Build the payload in a buffer that has the required size.
         *
         * @param buffer The buffer used to construct the payload.
         */
        virtual void build(const Span<uint8_t> &buffer) const = 0;

    protected:
        /**
         * Non virtual destructor.
         */
        ~PayloadBuilder() { }
    };

    /**
     * Create a new MessageBuilder that can be used to construct valid NDEF
     * messages.
     *
     * @param buffer The data buffer that will contain the NDEF message.
     */
    MessageBuilder(const Span<uint8_t> &buffer);

    /**
     * Append a new record to the message being built.
     *
     * @param type The type of the record to insert.
     * @param payload The payload of the record (optional).
     * @param is_last_record true if the record to insert is the last record of
     * the payload or false otherwise.
     *
     * @return true if the record has been successfully inserted or false
     * otherwise.
     *
     * @note insertion can fail if the message is already complete or if the
     * size remaining in the message buffer is not large enough to makes the
     * record inserted fit.
     */
    bool append_record(
        const RecordType &type,
        const RecordPayload &payload = RecordPayload(),
        bool is_last_record = false
    );

    /**
     * Append a new record to the message being built.
     *
     * @param type The type of the record to insert.
     * @param builder The builder of the payload.
     * @param is_last_record true if the record to insert is the last record of
     * the payload or false otherwise.
     *
     * @return true if the record has been successfully inserted or false
     * otherwise.
     *
     * @note insertion can fail if the message is already complete or if the
     * size remaining in the message buffer is not large enough to makes the
     * record inserted fit.
     */
    bool append_record(
        const RecordType &type,
        const PayloadBuilder &builder,
        bool is_last_record = false
    );

    /**
     * Append a new record to the message being built.
     *
     * @param record The record to insert.
     * @param builder The builder that will construct the payload.
     *
     * @return true if the record has been successfully inserted or false otherwise.
     *
     * @note insertion can fail if the message is already complete or if the
     * size remaining in the message buffer is not large enough to makes the
     * record inserted fit.
     */
    bool append_record(
        const Record &record,
        const PayloadBuilder *builder = NULL
    );

    /**
     * Compute the size of a record.
     *
     * @param record The record used to compute the size.
     * @param builder The payload builder if any.
     *
     * @return The size of the payload for the record in input.
     */
    static size_t compute_record_size(
        const Record &record,
        const PayloadBuilder *builder = NULL
    );

    /**
     * Reset the builder state.
     */
    void reset();

    /**
     * Reset the builder state and assign a new buffer to it.
     */
    void reset(const Span<uint8_t> &buffer);

    /**
     * Return true if the message stored is complete and false otherwise.
     *
     * @return true if the message is complete or false.
     */
    bool is_message_complete() const;

    /**
     * Return the buffer storing the data if the message is complete or an empty
     * buffer if the message is not complete.
     *
     * @return The message built.
     */
    Span<const uint8_t> get_message() const;

private:
    // append fields
    void append_header(const Record &record, const PayloadBuilder *);
    void append_type_length(const Record &record);
    void append_payload_length(const Record &, const PayloadBuilder *);
    void append_id_length(const Record &);
    void append_type(const Record &);
    void append_id(const Record &);
    void append_payload(const Record &, const PayloadBuilder *);

    // helpers
    static bool is_short_payload(const Record &record, const PayloadBuilder *);
    static size_t get_payload_size(const Record &, const PayloadBuilder *);

    // builder state.
    Span<uint8_t> _message_buffer;
    size_t _position;
    bool _message_started;
    bool _message_ended;
    bool _in_chunk;
};

/**
 * @}
 */

} // namespace ndef
} // namespace nfc
} // namespace mbed

#endif /* NFC_NDEF_MESSAGEBUILDER_H_ */
