//
// File: sacn.h
// Author: Peter Slattery
// Creation Date: 2020-01-01
//
#ifndef SACN_H

#define NETWORKINTID_INVALID -1

#define DEFAULT_STREAMING_ACN_PORT 5568

#define IP_ADDRESS_BYTES 16
#define STARTCODE_DMX 0

/* 
 * a description of the address space being used
 */
#define PREAMBLE_SIZE_ADDR 0
#define POSTAMBLE_SIZE_ADDR 2
#define ACN_IDENTIFIER_ADDR 4
#define ROOT_FLAGS_AND_LENGTH_ADDR 16
#define ROOT_VECTOR_ADDR 18
#define CID_ADDR 22
#define FRAMING_FLAGS_AND_LENGTH_ADDR 38
#define FRAMING_VECTOR_ADDR 40
#define SOURCE_NAME_ADDR 44
#define PRIORITY_ADDR 108
#define RESERVED_ADDR 109
#define SEQ_NUM_ADDR 111
#define OPTIONS_ADDR 112
#define UNIVERSE_ADDR 113
#define DMP_FLAGS_AND_LENGTH_ADDR 115
#define DMP_VECTOR_ADDR 117
#define DMP_ADDRESS_AND_DATA_ADDR 118
#define FIRST_PROPERTY_ADDRESS_ADDR 119
#define ADDRESS_INC_ADDR 121
#define PROP_COUNT_ADDR 123
#define START_CODE_ADDR 125
#define PROP_VALUES_ADDR (START_CODE_ADDR + 1)

/*
 * common sizes
 */
#define STREAM_HEADER_SIZE 126
#define STREAM_BODY_SIZE 512

#define SOURCE_NAME_SIZE 64
#define RLP_PREAMBLE_SIZE 16
#define RLP_POSTAMBLE_SIZE 0
#define ACN_IDENTIFIER_SIZE 12

/*
 * data definitions
 */
#define ACN_IDENTIFIER "ASC-E1.17\0\0\0"	
#define ROOT_VECTOR 4
#define FRAMING_VECTOR 2
#define DMP_VECTOR 2
#define ADDRESS_AND_DATA_FORMAT 0xa1
#define ADDRESS_INC 1
#define DMP_FIRST_PROPERTY_ADDRESS_FORCE 0
#define RESERVED_VALUE 0

//for support of the early draft
#define DRAFT_STREAM_HEADER_SIZE 90
#define DRAFT_SOURCE_NAME_SIZE 32

//for support of the early draft
#define DRAFT_ROOT_VECTOR 3

const u32 VHD_MAXFLAGBYTES = 7;  //The maximum amount of bytes used to pack the flags, len, and vector
const u32 VHD_MAXLEN = 0x0fffff;  //The maximum packet length is 20 bytes long
const u32 VHD_MAXMINLENGTH = 4095;  //The highest length that will fit in the "smallest" length pack

//Defines for the VHD flags
const u8 VHD_L_FLAG = 0x80;
const u8 VHD_V_FLAG = 0x40;
const u8 VHD_H_FLAG = 0x20;
const u8 VHD_D_FLAG = 0x10;

#define CID_Bytes 16
struct cid
{
    u8 Bytes[CID_Bytes];
};

struct streaming_acn
{
    platform_socket_handle SendSocket;
    cid CID;
    s32 SequenceIterator;
};

///////////////////////////////////////////////
//
//         SACN Data Header Functions
//
///////////////////////////////////////////////

internal void
SetStreamHeaderSequence_ (u8* Buffer, u8 Sequence, b32 Draft)
{
    DEBUG_TRACK_FUNCTION;
    PackB1(Buffer + SEQ_NUM_ADDR, Sequence);
}

internal void
VHD_PackFlags_(u8* Buffer, b32 InheritVec, b32 InheritHead, b32 InheritData)
{
    u8* Cursor = Buffer;
    u8 NewByte = UpackB1(Cursor) & 0x8f;
    
    if (!InheritVec) { NewByte |= VHD_V_FLAG; }
    if (!InheritHead) { NewByte |= VHD_H_FLAG; }
    if (!InheritData) { NewByte |= VHD_D_FLAG; }
    
    PackB1(Cursor, NewByte);
}

internal u8*
VHD_PackLength_(u8* Buffer, u32 Length, b32 IncludeLength)
{
    u8* Cursor = Buffer;
    u32 AdjustedLength = Length;
    if (IncludeLength)
    {
        if (Length + 1 > VHD_MAXMINLENGTH)
        {
            AdjustedLength += 2;
        }
        else
        {
            AdjustedLength += 1;
        }
    }
    
    // Mask out the length bits to keep flags intact
    u8 NewByte = UpackB1(Cursor) & 0x70;
    if (AdjustedLength > VHD_MAXMINLENGTH)
    {
        NewByte |= VHD_L_FLAG;
    }
    
    u8 PackBuffer[4];
    PackB4(PackBuffer, AdjustedLength);
    if (AdjustedLength <= VHD_MAXMINLENGTH)
    {
        NewByte |= (PackBuffer[2] & 0x0f);
        Cursor = PackB1(Cursor, NewByte);
        Cursor = PackB1(Cursor, PackBuffer[3]);
    }
    else
    {
        NewByte |= (PackBuffer[1] & 0x0f);
        Cursor = PackB1(Cursor, PackBuffer[2]);
        Cursor = PackB1(Cursor, PackBuffer[3]);
    }
    
    return Cursor;
}

internal cid 
StringToCID_ (const char* String)
{
    cid Result = {};
    
    const char* Src = String;
    u8* Dest = &Result.Bytes[0];
    b32 FirstNibble = true;
    
    while(*Src && (Dest - &Result.Bytes[0] < CID_Bytes))
    {
        u8 Offset = 0;
        if ((*Src >= 0x30) && (*Src <= 0x39)){ Offset = 0x30; }
        else if ((*Src >= 0x41) && (*Src <= 0x46)) { Offset = 0x37; }
        else if ((*Src >= 0x61) && (*Src <= 0x66)) { Offset = 0x66; }
        
        if (Offset != 0)
        {
            if (FirstNibble)
            {
                *Dest = (u8)(*Src - Offset);
                *Dest <<= 4;
                FirstNibble = false;
            }
            else
            {
                *Dest |= (*Src - Offset);
                Dest++;
                FirstNibble = true;
            }
        }
        Src++;
    }
    
    return Result; 
}

internal void
InitStreamHeader (u8* Buffer, s32 BufferSize, 
                  u16 SlotCount,
                  u8 StartCode,
                  u16 Universe,
                  u8 Priority,
                  u16 Reserved,
                  u8 Options,
                  const char* SourceName,
                  cid CID
                  )
{
    
    u8* Cursor = Buffer;
    
    // Preamble Size
    Cursor = PackB2(Cursor, RLP_PREAMBLE_SIZE);
    Cursor = PackB2(Cursor, RLP_POSTAMBLE_SIZE);
    
    GSMemCopy(ACN_IDENTIFIER, Cursor, ACN_IDENTIFIER_SIZE);
    Cursor += ACN_IDENTIFIER_SIZE;
    
    // TODO(Peter): If you never use this anywhere else, go back and remove the parameters
    VHD_PackFlags_(Cursor, false, false, false);
    Cursor = VHD_PackLength_(Cursor, 
                             STREAM_HEADER_SIZE - RLP_PREAMBLE_SIZE + SlotCount, 
                             false);
    
    // root vector
    Cursor = PackB4(Cursor, ROOT_VECTOR);
    
    // CID Pack
    for (s32 i = 0; i < CID_Bytes; i++)
    {
        *Cursor++ = CID.Bytes[i];
    }
    
    VHD_PackFlags_(Cursor, false, false, false);
    Cursor = VHD_PackLength_(Cursor, 
                             STREAM_HEADER_SIZE - FRAMING_FLAGS_AND_LENGTH_ADDR + SlotCount, 
                             false);
    
    // framing vector
    Cursor = PackB4(Cursor, FRAMING_VECTOR);
    
    // framing source name
    // :Check
    GSMemCopy(SourceName, (char*)Cursor, SOURCE_NAME_SIZE);
    Cursor[SOURCE_NAME_SIZE - 1] = '\0';
    Cursor += SOURCE_NAME_SIZE;
    
    // priority
    Cursor = PackB1(Cursor, Priority);
    
    // reserved
    Cursor = PackB2(Cursor, Reserved);
    
    // Sequence # is always set to 0/NONE at the beginning, but it is incremented when sending data
    Cursor = PackB1(Cursor, 0);
    
    // Options
    Cursor = PackB1(Cursor, Options);
    
    // Universe
    Cursor = PackB2(Cursor, Universe);
    
    VHD_PackFlags_(Cursor, false, false, false);
    Cursor = VHD_PackLength_(Cursor, 
                             STREAM_HEADER_SIZE - DMP_FLAGS_AND_LENGTH_ADDR + SlotCount,
                             false);
    
    // DMP Vector
    Cursor = PackB1(Cursor, DMP_VECTOR);
    
    // DMP Address and data type
    Cursor = PackB1(Cursor, ADDRESS_AND_DATA_FORMAT);
    
    // DMP first property address
    Cursor = PackB2(Cursor, 0);
    
    // DMP Address Increment
    Cursor = PackB2(Cursor, ADDRESS_INC);
    
    // Property Value Count -- Includes one byte for start code
    Cursor = PackB2(Cursor, SlotCount + 1);
    
    Cursor = PackB1(Cursor, StartCode);
    
    Assert(Cursor - Buffer == STREAM_HEADER_SIZE);
}

//
// New SACN
//

internal streaming_acn
InitializeSACN ( context Context)
{
    streaming_acn SACN = {};
    
    s32 Multicast_TimeToLive = 20;
    SACN.SendSocket = Context.PlatformGetSocketHandle(Multicast_TimeToLive);
    SACN.CID = StringToCID_ ("{67F9D986-544E-4abb-8986-D5F79382586C}");
    
    return SACN;
}

internal void
SACNCleanup(streaming_acn* SACN, context Context)
{
    Context.PlatformCloseSocket(SACN->SendSocket);
}

internal void
SACNUpdateSequence (streaming_acn* SACN)
{
    // Never use 0 after the first one
    if (++SACN->SequenceIterator == 0) 
    {
        ++SACN->SequenceIterator;
    }
}

internal void
SACNPrepareBufferHeader (s32 Universe, u8* Buffer, s32 BufferSize, s32 SizeReservedForHeader, streaming_acn SACN)
{
    Assert(SizeReservedForHeader == STREAM_HEADER_SIZE);
    Assert(Buffer && BufferSize > 0);
    
    s32 Priority = 0;
    InitStreamHeader(Buffer, BufferSize, STREAM_BODY_SIZE, STARTCODE_DMX, Universe, Priority, 0, 0, "Lumenarium", SACN.CID);
    SetStreamHeaderSequence_(Buffer, SACN.SequenceIterator, false);
}

internal u32
SACNGetUniverseSendAddress(s32 Universe)
{
    u8 MulticastAddressBuffer[4] = {};
    MulticastAddressBuffer[0] = 239;
    MulticastAddressBuffer[1] = 255;
    MulticastAddressBuffer[2] = (u8)((Universe & 0xff00) >> 8); // high bit
    MulticastAddressBuffer[3] = (u8)((Universe & 0x00ff)); // low bit
    
    u32 V4Address = (u32)UpackB4(MulticastAddressBuffer);
    return V4Address;
}


#define SACN_H
#endif // SACN_H