/* -LICENSE-START-
 ** Copyright (c) 2016 Blackmagic Design
 **
 ** Permission is hereby granted, free of charge, to any person or organization
 ** obtaining a copy of the software and accompanying documentation covered by
 ** this license (the "Software") to use, reproduce, display, distribute,
 ** execute, and transmit the Software, and to prepare derivative works of the
 ** Software, and to permit third-parties to whom the Software is furnished to
 ** do so, all subject to the following:
 **
 ** The copyright notices in the Software and this entire statement, including
 ** the above license grant, this restriction and the following disclaimer,
 ** must be included in all copies of the Software, in whole or in part, and
 ** all derivative works of the Software, unless such copies or derivative
 ** works are solely in the form of machine-executable object code generated by
 ** a source language processor.
 **
 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 ** DEALINGS IN THE SOFTWARE.
 ** -LICENSE-END-
 */

#ifndef __CEA708_ENCODER_H__
#define __CEA708_ENCODER_H__
#include <queue>
#include <vector>
#include <stdint.h>
#include "CEA708_Commands.h"

namespace CEA708
{
	
/* CEA-708 is encoded as a layered protocol as described in "3 Caption Channel Layered Protocol"
 *
 * Briefly:
 * - characters and commands are packed into service blocks
 * - service blocks are packed into channel packets
 * - channel packets are packed into caption distribution packets
 * - caption distribution packets are packed into ancillary data packets
 * - ancillary data packets are written into the luma channel of a vanc line
 */


// SMPTE 334-2 Table 3 – CDP Frame Rate
enum CDPFrameRate
{
	cdpFrameRate_Forbidden = 0,	// '0b0000'
	cdpFrameRate_2397,			// '0b0001'
	cdpFrameRate_24,			// '0b0010'
	cdpFrameRate_25,			// '0b0011'
	cdpFrameRate_2997,			// '0b0100'
	cdpFrameRate_30,			// '0b0101'
	cdpFrameRate_50,			// '0b0110'
	cdpFrameRate_5994,			// '0b0111'
	cdpFrameRate_60				// '0b1000'
};

typedef std::vector<uint8_t> EncodedCaptionDistributionPacket;
typedef std::queue<EncodedCaptionDistributionPacket> CDPQueue;
class ServiceBlockEncoder;
class CaptionChannelPacketEncoder;
class CaptionDistributionPacketEncoder;


// CEA-708 6 DTVCC Service Layer
// Note: Extended Service Block Header not shown
class ServiceBlockEncoder
{
private:
	enum
	{
		kHeaderSize = 1,
		kMaximumData = 31
	};
	
	CaptionChannelPacketEncoder&	m_packetEncoder;
	uint8_t							m_serviceNumber;
	uint8_t							m_block[kHeaderSize + kMaximumData];
	uint8_t							m_blockSize;
	
	void updateHeader();
	void reset();
	
public:
	ServiceBlockEncoder(CaptionChannelPacketEncoder& packetEncoder, uint8_t serviceNumber);
	
	/* Add caption data to the service block.
	 * `buffer` is an indivisble unit (single character or syntactic element) and count must be <= 31 bytes.
	 * When full, updates header and pushes encoded service block to CaptionChannelPacketEncoder. */
	void push(const uint8_t* buffer, uint8_t count);
	
	/* Updates header and pushes encoded service block to CaptionChannelPacketEncoder.
	 * Flush cascades down the protocol stack.
	 * Calling flush() on an empty service block will generate a pad packet containing the service description with no data */
	void flush();
};


// CEA-708 5 DTVCC Packet Layer
class CaptionChannelPacketEncoder
{
private:
	enum
	{
		kHeaderSize = 1,
		kMaximumData = 127
	};
	
	CaptionDistributionPacketEncoder&	m_CDPEncoder;
	uint8_t								m_sequence;
	uint8_t								m_packet[kHeaderSize + kMaximumData];
	uint8_t								m_packetSize;
	
	void updateHeader();
	void reset();
	uint8_t paddedSize();
	
public:
	
	explicit CaptionChannelPacketEncoder(CaptionDistributionPacketEncoder& cdpEncoder);
	
	/* Add an encoded service block to the caption channel packet.
	 * When full, updates header and pushes encoded caption channel packet to CaptionDistributionPacketEncoder */
	void push(const uint8_t* block, uint8_t blockLength);
	
	/* Updates header and pushes encoded service block to CaptionDistributionPacketEncoder.
	 * Flush cascades down the protocol stack. */
	void flush();
};

	
// SMPTE 334-2 5 CDP Detailed Specification
class CaptionDistributionPacketEncoder
{
private:
	CDPQueue&				m_cdpQueue;
	uint16_t				m_sequence;
	uint8_t					m_CCCount;
	CDPFrameRate			m_frameRate;
	std::vector<uint8_t>	m_payload;
	
	void encode_ccdata(uint8_t*& buffer);
	void encode_svcinfo(uint8_t*& buffer);
	
	EncodedCaptionDistributionPacket encode();
	
	void reset();
	
public:
	CaptionDistributionPacketEncoder(CDPQueue& cdpQueue, int64_t frameDuration, int64_t timeScale);
	
	inline std::size_t maxPayloadSize() const
	{
		return m_CCCount * 2;
	}
	
	/* Add an encoded caption channel packet to the caption distribution packet.
	 * When full, encodes the cdp and pushes the completed CDP onto the CDPQueue. */
	void push(const uint8_t* packet, uint8_t packetLength);
	
	/* Encodes CDP and pushes the completed CDP onto the CDPQueue */
	void flush();
};


/* Helper class which initialises the encoder stack and provides a simple (limited)
 * iostream-like interface to generate closed caption data. */
class Encoder
{
private:
	CDPQueue							m_cdpQueue;
	CaptionDistributionPacketEncoder	m_cdpEncoder;
	CaptionChannelPacketEncoder			m_packetEncoder;
	ServiceBlockEncoder					m_serviceBlockEncoder;
	
public:
	Encoder(int64_t frameDuration, int64_t timeScale);

	// Push the given command / caption text into the encoder stack
	Encoder& operator<<(const SyntacticElement& command);
	Encoder& operator<<(const char* captionText);
	
	// True if there are no fully-encoded packets in the queue.
	bool empty() const;
	
	// Pop an encoded CDP from the queue
	bool pop(EncodedCaptionDistributionPacket* packet);
	
	// Flush any remaining data through the encoder stack.
	// If there was no partial data a pad packet is generated.
	void flush();
};

}
	
#endif
