/*
 Written by Yotam Mann, The Center for New Music and Audio Technologies,
 University of California, Berkeley.  Copyright (c) 2012, The Regents of
 the University of California (Regents).
 
 Permission to use, copy, modify, distribute, and distribute modified versions
 of this software and its documentation without fee and without a signed
 licensing agreement, is hereby granted, provided that the above copyright
 notice, this paragraph and the following two paragraphs appear in all copies,
 modifications, and distributions.
 
 IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
 OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS
 BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
 HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
 MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 For bug reports and feature requests please email me at yotam@cnmat.berkeley.edu
 */

#include "OSCBundle.h"
#include <stdlib.h>

 /*=============================================================================
	CONSTRUCTORS / DESTRUCTOR
=============================================================================*/

OSCBundle::OSCBundle(uint64_t _timetag){
    setTimetag(_timetag);
    numMessages = 0;
    error = OSC_OK;
    messages = NULL;
    incomingBuffer = NULL;
    incomingBufferSize = 0;
    decodeState = STANDBY;
}

OSCBundle::~OSCBundle(){
    for (int i = 0; i < numMessages; i++){
        OSCMessage * msg = getOSCMessage(i);
        delete msg;
    }
    free(messages);
    free(incomingBuffer);
}

//clears all of the OSCMessages inside
void OSCBundle::empty(){
    error = OSC_OK;
    for (int i = 0; i < numMessages; i++){
        OSCMessage * msg = getOSCMessage(i);
        delete msg;
    }
    free(messages);
    messages = NULL;
    clearIncomingBuffer();
    numMessages = 0;
}

/*=============================================================================
 SETTERS
 =============================================================================*/

OSCMessage & OSCBundle::add(char * _address){
	OSCMessage * msg = new OSCMessage(_address);
    if (!msg->hasError()){
        //realloc the array to fit the message
        OSCMessage ** messageMem = (OSCMessage **) realloc(messages, sizeof(OSCMessage *) * (numMessages + 1));
        if (messageMem != NULL){
            messages = messageMem;
            messages[numMessages] = msg;
            numMessages++;
        } else {
            error = ALLOCFAILED;
        }
    }
    return *msg;
}

OSCMessage & OSCBundle::add(){
	OSCMessage * msg = new OSCMessage();
    //realloc the array to fit the message
    OSCMessage ** messageMem = (OSCMessage **) realloc(messages, sizeof(OSCMessage *) * (numMessages + 1));
    if (messageMem != NULL){
        messages = messageMem;
        messages[numMessages] = msg;
        numMessages++;
    } else {
        error = ALLOCFAILED;
    }
    return *msg;
}

OSCMessage & OSCBundle::add(OSCMessage & _msg){
    OSCMessage * msg = new OSCMessage(_msg);
    if (!msg->hasError()){
        //realloc the array to fit the message
        OSCMessage ** messageMem = (OSCMessage **) realloc(messages, sizeof(OSCMessage *) * (numMessages + 1));
        if (messageMem != NULL){
            messages = messageMem;
            messages[numMessages] = msg;
            numMessages++;
        } else {
            error = ALLOCFAILED;
        }
    }
    return *msg;
}

/*=============================================================================
    GETTERS
 =============================================================================*/

//returns the first fullMatch.
OSCMessage * OSCBundle::getOSCMessage( char * addr){
	for (int i = 0; i < numMessages; i++){
        OSCMessage * msg = getOSCMessage(i);
        if (msg->fullMatch(addr)){
            return msg;
        }
	}
}

//the position is the same as the order they were declared in
OSCMessage * OSCBundle::getOSCMessage(int pos){
	if (pos < numMessages){
		return messages[pos];
	} 
}

/*=============================================================================
    PATTERN MATCHING
 =============================================================================*/


bool OSCBundle::dispatch(const char * pattern, void (*callback)(OSCMessage&), int initial_offset){
	bool called = false;
	for (int i = 0; i < numMessages; i++){
        OSCMessage msg = getOSCMessage(i);
		called |= msg.dispatch(pattern, callback, initial_offset);
	}
	return called;
}


bool OSCBundle::route(const char * pattern, void (*callback)(OSCMessage&, int), int initial_offset){
	bool called = false;
	for (int i = 0; i < numMessages; i++){
        OSCMessage msg = getOSCMessage(i);
		called |= msg.route(pattern, callback, initial_offset);
	}
	return called;
}

/*=============================================================================
    SIZE
 =============================================================================*/


int OSCBundle::size(){
	return numMessages;
}

/*=============================================================================
 ERROR HANDLING
 =============================================================================*/

bool OSCBundle::hasError(){
    bool retError = error != OSC_OK;
    //test each of the data
    for (int i = 0; i < numMessages; i++){
        OSCMessage * msg = getOSCMessage(i);
        retError |= msg->hasError();
    }
	return retError;
}

OSCErrorCode OSCBundle::getError(){
    return error;
}


/*=============================================================================
 SENDING
 =============================================================================*/

void OSCBundle::send(Print &p){
    //don't send a bundle with errors
    if (hasError()){
        return;
    }
    //write the bundle header
    static uint8_t header[] = {'#', 'b', 'u', 'n', 'd', 'l', 'e', 0};
    p.write(header, 8);
    //write the timetag
    uint64_t t64 = BigEndian(timetag);
    uint8_t * tptr = (uint8_t *) &t64;
    p.write(tptr, 8);
    //send the messages
    for (int i = 0; i < numMessages; i++){
        OSCMessage * msg = getOSCMessage(i);
        int msgSize = msg->bytes();
        //turn the message size into a pointer
        uint32_t s32 = BigEndian((uint32_t) msgSize);
        uint8_t * sptr = (uint8_t *) &s32;
        //write the messsage size
        p.write(sptr, 4);
        msg->send(p);
    }
}

/*=============================================================================
    FILLING
 =============================================================================*/

void OSCBundle::fill(uint8_t incomingByte){
    decode(incomingByte);
}

void OSCBundle::fill(uint8_t * incomingBytes, int length){
    while (length--){
        decode(*incomingBytes++);
    }
}

/*=============================================================================
    DECODING
 =============================================================================*/

void OSCBundle::decodeTimetag(){
    //parse the incoming buffer as a uint64
    setTimetag(incomingBuffer);
    //make sure the endianness is right
    timetag = BigEndian(timetag);
    decodeState = MESSAGE_SIZE;
    clearIncomingBuffer();
}

void OSCBundle::decodeHeader(){
    const char * header = "#bundle";
    if (strcmp(header, (char *) incomingBuffer)!=0){
        //otherwise go back to the top and wait for a new bundle header
        decodeState = STANDBY;
        error = INVALID_OSC;
    } else {
        decodeState = TIMETAG;
    }
    clearIncomingBuffer();
}

void OSCBundle::decodeMessage(uint8_t incomingByte){
    //get the current message
    if (numMessages > 0){
       OSCMessage * lastMessage = messages[numMessages - 1];
        //put the bytes in there
        lastMessage->fill(incomingByte);
        //if it's all done
        if (incomingBufferSize == incomingMessageSize){
            //move onto the next message
            decodeState = MESSAGE_SIZE;
            clearIncomingBuffer();
        } else if (incomingBufferSize > incomingMessageSize){
            error = INVALID_OSC;
        }
    }
}

//does not validate the incoming OSC for correctness
void OSCBundle::decode(uint8_t incomingByte){
    addToIncomingBuffer(incomingByte);
    switch (decodeState){
        case STANDBY:
            if (incomingByte == '#'){
                decodeState = HEADER;
            } else if (incomingByte == '/'){
                decodeState = MESSAGE;
            }
            break;
        case HEADER:
			if (incomingBufferSize == 8){
                decodeHeader();
                decodeState = TIMETAG;
            }
			break;
		case TIMETAG:
            if (incomingBufferSize == 8){
                decodeTimetag();
                decodeState = MESSAGE_SIZE;
            }
			break;
		case MESSAGE_SIZE:
            if (incomingBufferSize == 4){
                //make sure the message size is valid
                int32_t msgSize;
                memcpy(&msgSize, incomingBuffer, 4);
                msgSize = BigEndian(msgSize);
                if (msgSize % 4 != 0 || msgSize == 0){
                    error = INVALID_OSC;
                } else {
                    //add a message to the buffer
                    decodeState = MESSAGE;
                    incomingMessageSize = msgSize;
                    clearIncomingBuffer();
                    //add a new empty message
                    add();
                }
            }
            break;
		case MESSAGE:
            decodeMessage(incomingByte);
            break;
    }
}


/*=============================================================================
 INCOMING BUFFER MANAGEMENT
 =============================================================================*/

void OSCBundle::addToIncomingBuffer(uint8_t incomingByte){
    //realloc some space for the new byte and stick it on the end
	incomingBuffer = (uint8_t *) realloc ( incomingBuffer, incomingBufferSize + 1);
	if (incomingBuffer != NULL){
		incomingBuffer[incomingBufferSize++] = incomingByte;
	} else {
		error = ALLOCFAILED;
	}
}

void OSCBundle::clearIncomingBuffer(){
    incomingBufferSize = 0;
    free(incomingBuffer);
    incomingBuffer = NULL;
}



