/*
 * DataRecorder.cpp
 *
 *   |block marker  |
 *   |block version |
 *   |size          | : next block = current offset + size
 *   |skip offset   | : next block = skip offset
 *   | counter      | : a free running counter
 *   | spare        |
 *   |--------------|
 *   |name          | : 16 char string, zero terminated
 *   |timetag       | : recording time (us)
 *   | batch tag    |
 *   |decorder_id   | : an ID used to decode the info
 *   |chunks        | : number of chunks
 *   |--------------|
 *   |chunk name    | : 16 chars
 */

//WARNING: never read from allocated block!!! Write only please

#include "DataRecorder.h"

#include "DataRecorderBlockFormat.h"

#include <stdint.h>
#include <string.h>

#include <stdint.h>

#include "mex_hobbit_register.h"

#include "mex.h"

#include "bsp_interrupts.h"
#include "bsp_cache.h"

//#include "bsp.h"

//#include "mck_hp_memory.h"
#include "rtlog.h"

#include <string.h>

#ifdef __MINGW32__
#pragma GCC optimize("-O0")
#endif

#ifdef _TMS320C6X

#include "c6x.h"

#include "bsp.h"

static unsigned int tus()
{
    return bsp_hrt_get()/1000;
}

static inline bool rec_coreN()
{
    return (DNUM!=0);
}

#include "mck_semaphore.h"
#include "mck_hp.h"

class RecorderManager;

extern RecorderManager rectrl_cx __attribute__((section(".mcsm_common")));

class RecorderCriticalSection
{
public:
    volatile unsigned int sts;
    void enter()
    {
        sts=bsp_interrupts_disable();
        mck_sem_acquire_indirect(mck_sem_reserved_shared_heap); //mck_hp_lock();
    }
    void leave()
    {
        mck_sem_release(mck_sem_reserved_shared_heap); //mck_hp_unlock();
        bsp_interrupts_restore(sts);
    }
};

#else
//Retargeting
class RecorderCriticalSection
{
public:
    volatile unsigned int sts;
    void enter()
    {
        sts=bsp_interrupts_disable();
    }
    void leave()
    {
        bsp_interrupts_restore(sts);
    }
};

static bool rec_coreN()
{
    return false;
}

static unsigned int tus()
{
    return 0;
}

#endif

static mex_hobbit::mex_hobbit_namespace recNamespace("rec");

extern void softrec_sata_forward(const void* p, unsigned int len) __attribute__((weak));


struct RecorderManager: public DataRecorderFormat
{
	typedef char* rec_buff_t;

    enum {max_rec_points=64};

    volatile unsigned int active;

    unsigned int size;
    unsigned int marker;

    unsigned int tot_recorded;
    unsigned int recorded;

    int reg_num;
    DataRecorderRaw* rec_points[max_rec_points];

    bool validateRecorderPoint(const DataRecorderRaw& p)
    {
        //if ((p.rec_reserved_id_<0) || (p.rec_reserved_id_>reg_num))
        //     return false;
        if (!buffer)
            return false;
        if (!active)
            return false;
        if (!p.enabled)
            return false;
        return true;
    }

    uint8_t* buffer;

    volatile unsigned int stream_mode;

    volatile unsigned int cursor;
    volatile unsigned int consumer;
    volatile unsigned int r_cursor;
    volatile unsigned int r_cursor0;

    volatile unsigned int batch_counter;
    volatile unsigned int signal_counter;
    volatile unsigned int t_signal;
    volatile unsigned int t_batch;

    volatile unsigned int max_rec_size;
    volatile unsigned int rec_total_size;

    volatile unsigned int sata;
    volatile unsigned int sata_saved;

    static void __monitor_invokabe_reset();

    //typedef Recorder::rec_header_t rec_header_t;

    volatile RecorderManager::Block* blockAlloc(const char* name, unsigned int marker, unsigned int total_size, unsigned int chunks)
    {
    	unsigned int s=sizeof(RecorderManager::Block)+total_size;

    	//@#AC 24/1/23: one chunks is already part of RecorderManager::Block, so add extra space only if more then one is required
    	//A part the waste of space, the extra, unused allocated memory cause a wrong clean up of the block trailer, leaving in place old markers that could confuse the decoders
    	if (chunks>1)
    		s+=chunks*sizeof(RecorderManager::Chunk);

    	unsigned int xs=s;

    	s+=sizeof(uint32_t)*2;//room for end marker
    	//round up to 256, required by SATA (was 128 for cache align constrains only)
    	if (s & 255U)
    	{
    	    s+=255;
    	    s=s & (~255U);
    	}

        RecorderCriticalSection cs;

        cs.enter();

        bool stop_me=false;

        if (max_rec_size)
        {
            if (rec_total_size>=max_rec_size)
            {
                stop_me=true;
            }
        }

        if (stream_mode)
        {
           if (cursor>=consumer)
           {
               if ((cursor+s)>=size)
               {
                   if (s<consumer)
                   {
                       r_cursor0=r_cursor;
                       r_cursor=cursor;
                       cursor=0;
                   }
                   else
                       stop_me=true;
               }
           }
           else if ((cursor+s)>=consumer)
           {
               stop_me=true;
           }
        }
        else if ((cursor+s)>=size)
        {
            stop_me=true;
        	//active=0;
        	//cs.leave();
            //rtlog_const_message("REC", rtlog_info|rtlog_breaking_news, "Recorded full");
        	//return 0;
        }

        if (stop_me)
        {
            active=0;
            cs.leave();
            rtlog_const_message("REC", rtlog_info|rtlog_breaking_news, "Recorded full");
            return 0;
        }

        unsigned int start_pos=cursor;

       volatile RecorderManager::Block* b=reinterpret_cast<volatile RecorderManager::Block*>(&buffer[cursor]);

       cursor+=s;
       ++tot_recorded;
       ++recorded;

       rec_total_size+=s;

       b->headerEssential.marker[0]=0;
       b->headerEssential.marker[1]=0;

       unsigned int skip_offset=cursor;

   	   cs.leave();


   	   memset((void*)b, 0, sizeof *b);

   	   if (marker==0)
   		   marker=0x5A5A5A5A;

       b->headerEssential.marker[0]=marker;
       b->headerEssential.marker[1]=marker;
       b->headerEssential.version=0;
       b->headerEssential.block_size=s-sizeof(RecorderManager::BlockEssentialHeader);
       b->headerEssential.block_header_size=sizeof b->header;

       b->header.skip_offset=skip_offset;
       b->header.counter=tot_recorded;
       b->header.b_counter=batch_counter;
       b->header.s_counter=signal_counter;
       b->header.b_timetag=t_batch;
       b->header.s_timetag=t_signal;

       b->header.timetag_us=tus();

       strncpy((char*)&b->data.blockName.name[0], name, sizeof b->data.blockName.name);
       b->data.chunks=chunks;
       b->data.decoder_info=0;
       b->header.extra_info_size=0;
       b->header.chunk_info_size=(sizeof b->data.chunk)-4;
       b->data.chunk.chunk_marker[0]=0x4E554843;
       b->data.chunk.chunk_marker[1]=0x4E554843;
       b->data.chunk.size=total_size;
       memcpy((void*)&b->data.chunk.data_marker, "DATADATA", sizeof b->data.chunk.data_marker);

       {//Fill align gap
           uint32_t* p=reinterpret_cast<uint32_t*>(&buffer[start_pos]);
           unsigned int s_w=s>>2;
           unsigned int xs_w=xs>>2;
           s_w=s_w-2;   //skip last two word
           for(unsigned int i=xs_w; i<s_w; ++i)
               p[i]=0;
       }

       {//Write end marker
           uint32_t* footer=reinterpret_cast<uint32_t*>(&buffer[(start_pos+s)-8]);
           footer[0]=~marker;
           footer[1]=~marker;
       }

       return b;
    }

    void registerRecodingPoint(DataRecorderRaw& recPoint)
    {
        RecorderCriticalSection cs;

        cs.enter();

        if (reg_num<max_rec_points)
        {
            recPoint.rec_reserved_id_=reg_num;
            rec_points[reg_num]=&recPoint;
            ++reg_num;
        }

        {
        	mex_hobbit_register_instance(recPoint.name, recPoint, &recNamespace);
        }

        cs.leave();
    }

    struct RecExtract
    {
    	unsigned int active;
    	const void* block;
    	unsigned int size;
    };

    void saveTerminated(unsigned int pos)
    {
        r_cursor=pos;
        r_cursor0=pos;
    }

    static void __monitor_invokable_start() __attribute__((used,noinline));
    static void __monitor_invokable_startsata() __attribute__((used,noinline));
    static void __monitor_invokable_stop() __attribute__((used,noinline));
};

//static RecorderManager cx;
/*static*/ RecorderManager rectrl_cx;
#define cx rectrl_cx

void RecorderManager::__monitor_invokable_start()
{
	RecorderCriticalSection cs;

	cs.enter();

    cx.active=0;
    cx.cursor=0;
    cx.recorded=0;
    cx.consumer=0;
    cx.stream_mode=0;
    cx.r_cursor=0;
    cx.r_cursor0=0;
    cx.rec_total_size=0;
    cx.sata=0;

    cx.active=1;

    cs.leave();

    rtlog_const_message("REC", rtlog_info|rtlog_breaking_news, "Recorded start");
}

void RecorderManager::__monitor_invokable_startsata()
{
    RecorderCriticalSection cs;

    cs.enter();

    cx.active=0;
    cx.cursor=0;
    cx.recorded=0;
    cx.consumer=0;
    cx.stream_mode=0;
    cx.r_cursor=0;
    cx.r_cursor0=0;
    cx.rec_total_size=0;

    cx.sata=1;
    cx.stream_mode=1;

    cx.active=1;

    cs.leave();

    rtlog_const_message("REC", rtlog_info|rtlog_breaking_news, "SATA Recording start");
}

void RecorderManager::__monitor_invokable_stop()
{
    cx.active=0;
    rtlog_const_message("REC", rtlog_info|rtlog_breaking_news, "Recorder stopped");
}

static volatile unsigned int recorder_cx_initialize;

void Recorder::initialize()
{
	recorder_cx_initialize=1;
	//Assuming CX is statically allocated, already initialized to zero
#if 0
    if (recorder_cx_initialize)
        return;
   RecorderCriticalSection cs;
   cs.enter();
   if (recorder_cx_initialize==0)
   {
       memset(&cx, 0, sizeof cx);
       recorder_cx_initialize=1;
   }
   cs.leave();
#endif
}

void Recorder::configure(void* buffer, unsigned int size)
{
    initialize();
	//if (cx.buffer)
	//	return;

    if (buffer==0)
    {
    	buffer=RecorderMemory::getMemory(buffer, size);
        if (!buffer)
            rtlog_printf("REC", rtlog_warning|rtlog_breaking_news, "Sorry, no available for recording");
    }

    mex_hobbit_register_instance("ctrl", cx, &recNamespace);
    cx.buffer=reinterpret_cast<uint8_t*>(buffer);
    cx.size=size;
    cx.cursor=0;

    rtlog_printf("REC", rtlog_info|rtlog_breaking_news, "@0x%8.8X * %u bytes (%u KiBytes)",
            (unsigned int)buffer, size, size/1024);

#ifdef _TMS320C6X
    bsp_cache_disable(buffer, size); //required for multicore recording
#endif
}


bool Recorder::recording()
{
	return cx.active;
}

bool Recorder::sataRecording()
{
	return cx.active && cx.sata;
}

unsigned int Recorder::start()
{
	cx.__monitor_invokable_start();
	return cx.size;
}

unsigned int Recorder::sataStart()
{
    cx.__monitor_invokable_startsata();
    return cx.size;
}

unsigned int Recorder::stop()
{
	cx.__monitor_invokable_stop();
	return cx.cursor;
}

void Recorder::setStreamMode(bool enable)
{
    cx.stream_mode=enable ? 1 : 0;
}

void Recorder::setBatchCounter(unsigned int batch, unsigned int time_us)
{
    cx.batch_counter=batch;
    cx.t_batch=time_us;
}

void Recorder::setSignalCounter(unsigned int signal, unsigned int time_us)
{
    cx.signal_counter=signal;
    cx.t_signal=time_us;
}

Recorder::FileInfo Recorder::getStream()
{
    cx.stream_mode=1;
    return getData();
}

Recorder::FileInfo Recorder::getData()
{
	FileInfo tmp;
	tmp.status=cx.active;
	tmp.msg=0;
	tmp.size=0;
	tmp.data=&cx.buffer[0];

	{
		RecorderCriticalSection cs;

		cs.enter();

		if ((cx.stream_mode) && (cx.consumer>cx.cursor))
		{
		    if (cx.consumer<cx.r_cursor)
		    {
	            tmp.data=&cx.buffer[cx.consumer];
	            tmp.size=cx.r_cursor-cx.consumer;
	            cx.consumer=0;
		    }
		    else
		    {
	            if (cx.consumer<cx.r_cursor0)
	            {
	                tmp.data=&cx.buffer[cx.consumer];
	                tmp.size=cx.r_cursor0-cx.consumer;
	                cx.consumer=0;
	                cx.r_cursor0=0;
	            }
	            else    //BUG????
	            {
                    cx.consumer=0;
                    tmp.data=&cx.buffer[cx.consumer];
                    tmp.size=cx.cursor-cx.consumer;
                    cx.consumer=cx.cursor;
	            }
		    }
		}
		else
		{
		    tmp.data=&cx.buffer[cx.consumer];
		    tmp.size=cx.cursor-cx.consumer;
		    cx.consumer=cx.cursor;
		}

		cs.leave();
	}
	return tmp;
}

DataRecorderRaw::DataRecorderRaw(int ena, const char* n, DataRecorderRaw::marker_t m):
    cache_synch_required(rec_coreN())
{
	if (!rec_coreN())
	{
		name=n;
		mark=m;
		enabled=ena;
		rec_counter=0;
		rec_saved=0;
		rec_reserved_id_=-1;

		registerMe();
	}
}

DataRecorderRaw::DataRecorderRaw(const char* n, DataRecorderRaw::marker_t m):
/*
	name(n),
	mark(m),
	enabled(false),
	rec_counter(0),
	rec_saved(0),
	rec_reserved_id_(-1),
	cache_synch_required(rec_coreN())
*/
    cache_synch_required(rec_coreN())
{
    if (!rec_coreN())
    {
        name=n;
        mark=m;
        enabled=false;
        rec_counter=0;
        rec_saved=0;
        rec_reserved_id_=-1;

        registerMe();
    }
}

DataRecorderRaw::~DataRecorderRaw()
{
}

void DataRecorderRaw::setEnable(bool ena)
{
    enabled=ena;
}

void DataRecorderRaw::setCacheSynchRequired(bool required)
{
    cache_synch_required=required ? 1 : 0;
}

bool DataRecorderRaw::cacheSynchRequired() const
{
    return cache_synch_required!=0;
}

void DataRecorderRaw::setName(const char* n)
{
	name=n;
}

void DataRecorderRaw::setMarker(marker_t marker)
{
	mark=marker;
}

void DataRecorderRaw::registerMe()
{
    Recorder::initialize();

	cx.registerRecodingPoint(*this);
}

void DataRecorderRaw::saveTerminated()
{
    cx.saveTerminated(s_cursor);
}

unsigned int DataRecorderRaw::blockSizeOverhead()
{
    return sizeof(RecorderManager::Block);
}

void* DataRecorderRaw::copyPrepare(DataRecorderRaw& d, unsigned int size, unsigned int type, unsigned int meta_size)
{
	bool ok=cx.validateRecorderPoint(d);
	if (!ok)
		return 0;

	++rec_counter;
	volatile RecorderManager::Block* b=cx.blockAlloc(name, mark, size, 1);
	if (!b)
		return 0;
	s_cursor=b->header.skip_offset;
	b->data.chunk.size=size;
	b->data.chunk.type=type;
	b->data.chunk.meta_size=meta_size;
	return (void*)&b->data.chunk.data[0];
}

void* DataRecorderRaw::noCopyPrepare(DataRecorderRaw& d, void* dst, unsigned int size, unsigned int type, unsigned int meta_size)
{
    bool ok=cx.validateRecorderPoint(d);
    if (!ok)
        return 0;

    RecorderManager::Block* b=reinterpret_cast<RecorderManager::Block*>(dst);

    uint32_t marker=mark==0 ? 0x5A5A5A5A : mark;

    uint32_t total_size=size;

    b->headerEssential.marker[0]=marker;
    b->headerEssential.marker[1]=marker;
    b->headerEssential.version=0;
    b->headerEssential.block_size=size-sizeof(RecorderManager::BlockEssentialHeader);
    b->headerEssential.block_header_size=sizeof b->header;

    b->header.skip_offset=0;
    b->header.counter=cx.tot_recorded;
    b->header.b_counter=cx.batch_counter;
    b->header.s_counter=cx.signal_counter;
    b->header.b_timetag=cx.t_batch;
    b->header.s_timetag=cx.t_signal;

    b->header.timetag_us=tus();

    strncpy((char*)&b->data.blockName.name[0], name, sizeof b->data.blockName.name);
    b->data.chunks=1;
    b->data.decoder_info=0;
    b->header.extra_info_size=0;
    b->header.chunk_info_size=(sizeof b->data.chunk)-4;
    b->data.chunk.chunk_marker[0]=0x4E554843;
    b->data.chunk.chunk_marker[1]=0x4E554843;
    b->data.chunk.size=total_size;
    memcpy((void*)&b->data.chunk.data_marker, "DATADATA", sizeof b->data.chunk.data_marker);

    b->data.chunk.size=size;
    b->data.chunk.type=type;
    b->data.chunk.meta_size=meta_size;

    return b;
}

void* DataRecorderRaw::copyRaw(volatile void* dst, const void* src, unsigned int len)
{
	uint8_t* p=const_cast<uint8_t*>(reinterpret_cast<volatile uint8_t*>(dst));
	memcpy(p, src, len);
	return &p[len];
}


static void recSata()
{
	if (cx.sata && (!rec_coreN()))
	{
	    if (softrec_sata_forward)
	    {
            Recorder::FileInfo fi=Recorder::getStream();
            if (fi.size)
            {
                softrec_sata_forward(fi.data, fi.size);
                ++cx.sata_saved;
            }
	    }
	    else
	    	cx.sata=0;
	}
}

bool DataRecorderRaw::saveRawNoSataFlush(const void* data, unsigned int size, unsigned int type)
{
    void* p=copyPrepare(*this, size, type);
    if (!p)
        return false;

    copyRaw(p, data, size);

    ++rec_saved;

    //recSata();
    return true;
}

bool DataRecorderRaw::saveRawPreFlush(const void* data, unsigned int size, unsigned int t)
{
    volatile unsigned int sts=bsp_interrupts_disable();
    recSata();

    void* p=copyPrepare(*this, size, t);

    bsp_interrupts_restore(sts);

    if (!p)
        return false;

    copyRaw(p, data, size);

    ++rec_saved;

    recSata();
    return true;
}

bool DataRecorderRaw::saveRaw(const void* data, unsigned int size, unsigned int type)
{
	void* p=copyPrepare(*this, size, type);
	if (!p)
		return false;

	copyRaw(p, data, size);

	++rec_saved;

	recSata();
	return true;
}

bool DataRecorderRaw::saveRaw2(const void* data1, unsigned int size1, const void* data2, unsigned int size2, unsigned int type)
{
	unsigned int size=size1+size2;
	unsigned char* p=reinterpret_cast<unsigned char*>(copyPrepare(*this, size, type));
	if (!p)
		return false;

	if (data1)
		copyRaw(p, data1, size1);
	if (data2)
		copyRaw(&p[size1], data2, size2);

	++rec_saved;

	recSata();
/*
	if (cx.sata)
	{
	    if (softrec_sata_forward)
	    {
            Recorder::FileInfo fi=Recorder::getStream();
            if (fi.size)
                softrec_sata_forward(fi.data, fi.size);
	    }
	    else
	    	cx.sata=0;
	}
*/
	return true;
}

Recorder::EntityInfo Recorder::getEntities()
{
	EntityInfo tmp;
	tmp.items=cx.reg_num;
	tmp.instances=cx.rec_points;

	return tmp;
}

void Recorder::sataFlush()
{
#ifdef _TMS320C6X  /*experimental, maybe applicable to all*/
    recSata();
#endif
}

#if 0
//MOved to DataRecorderMemoryDefault.cpp
#ifdef _TMS320C6X
#else

#ifdef __MINGW32__
#include <stdlib.h>

void* RecorderMemory::getMemory(void* buffer, unsigned int& size)
{
	if (!buffer)
	{
		if (size==0)
			size=16*1024*1024;
		buffer=malloc(size);
	}
	return buffer;
}

#endif

#endif

#endif




