/*
* \file     mem_buff_demo.cpp
* \brief    OpenCSD: using the library with memory buffers for data.
* 
* \copyright  Copyright (c) 2018, ARM Limited. All Rights Reserved.
*/

/*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/* Example showing techniques to drive library using only memory buffers as input data 
 * and image data, avoiding file i/o in main processing routines. (File I/O used to 
 * initially populate buffers but this can be replaced if data is generated by a client 
 * environment running live.)
 */

#include <cstdio>
#include <string>
#include <iostream>
#include <sstream>
#include <cstring>

#include "opencsd.h"              // the library

/* Input trace buffer */
static uint8_t *input_trace_data = 0;  
static uint32_t input_trace_data_size = 0;

/* program memory image for decode */
static uint8_t *program_image_buffer = 0; // buffer for image data.
static uint32_t program_image_size = 0;   // size of program image data.
static ocsd_vaddr_t program_image_address = 0;  // load address on target of program image.

/* a message logger to pass to the error logger / decoder. */
static ocsdMsgLogger logger;

static bool use_callback = false;
static bool use_logfile = false;
static bool no_print = false;

    /* the file names to create the data buffers */
#ifdef _WIN32
static const char *default_base_snapshot_path = "..\\..\\..\\snapshots";
static const char *juno_snapshot = "\\juno_r1_1\\";
#else
static const char *default_base_snapshot_path = "../../../snapshots";
static const char *juno_snapshot = "/juno_r1_1/";
#endif

static const char *usr_snapshot_path = 0;
#define MAX_TRACE_FILE_PATH_LEN 512

/* logger callback class - print out error strings.
 * Example of custom log printer callback 
 */
class logCallback : public ocsdMsgLogStrOutI
{
public:
    logCallback()
    {
        out_console = true;
        out_file = false;
    }

    virtual ~logCallback()
    {
        closeOutFile();
    }

    virtual void printOutStr(const std::string& outStr)
    {
        if (out_console)
            std::cout << outStr.c_str();
        printOutFile(outStr);
    }

    void setOutConsole(const bool console_output)
    {
        out_console = console_output;
    }

    void setOutFile(const std::string &filename)
    {
        openOutFile(filename);
    }

private:
    void openOutFile(const std::string &filename)
    {
        
        m_out_file.open(filename.c_str(), std::fstream::out | std::fstream::app);
        if (m_out_file.is_open())
            out_file = true;
    }

    void printOutFile(const std::string& str)
    {
        if (out_file)
        {
            m_out_file << str;
        }
    }

    void closeOutFile()
    {
        if (out_file) {
            // close output file
            m_out_file.close();
        }
    }
    bool out_file;
    bool out_console;
    std::fstream m_out_file;
};
static logCallback logCB;

/* Decode tree is the main decoder framework - contains the frame unpacker, 
   packet and trace stream decoders, plus memory image references */
static DecodeTree *pDecoder = 0; 

/* an error logger - Decode tree registers all components with the error logger
so that errors can be correctly attributed and printed if required
*/
static ocsdDefaultErrorLogger err_log;  

/* callbacks used by the library */

// program memory image callback definition
uint32_t  mem_access_callback_fn(const void *p_context, const ocsd_vaddr_t address, const ocsd_mem_space_acc_t mem_space, const uint32_t reqBytes, uint8_t *byteBuffer);

// callback for the decoder output elements
class DecoderOutputProcessor : public ITrcGenElemIn
{
public:
    DecoderOutputProcessor() {};
    virtual ~DecoderOutputProcessor() {};

    virtual ocsd_datapath_resp_t TraceElemIn(const ocsd_trc_index_t index_sop,
        const uint8_t trc_chan_id,
        const OcsdTraceElement &elem)
    {
        // must fully process or make a copy of data in here.
        // element reference only valid for scope of call. 

        // for the example program we will stringise and print - 
        // but this is a client program implmentation dependent.
        std::string elemStr;
        std::ostringstream oss;
        oss << "Idx:" << index_sop << "; ID:" << std::hex << (uint32_t)trc_chan_id << "; ";
        elem.toString(elemStr);
        oss << elemStr << std::endl;
        logger.LogMsg(oss.str());
        return OCSD_RESP_CONT;
    }
}; 
static DecoderOutputProcessor output;

/* for test purposes we are initialising from files, but this could be generated test data as
 part of a larger program and / or compiled in memory images.

 We have hardcoded in one of the snapshots supplied with the library
 */
static int initDataBuffers()
{
    FILE *fp;
    std::string filename;
    long size;
    size_t bytes_read;

    /* trace data and memory file dump names and values - taken from snapshot metadata */
    static const char *trace_data_filename = "cstrace.bin";
    static const char *memory_dump_filename = "kernel_dump.bin";
    static ocsd_vaddr_t mem_dump_address = 0xFFFFFFC000081000;
    
    /* load up the trace data */
    filename = (usr_snapshot_path ? usr_snapshot_path : default_base_snapshot_path);
    filename += (std::string)juno_snapshot;
    filename += (std::string)trace_data_filename;

    fp = fopen(filename.c_str(), "rb");
    if (!fp)
        return OCSD_ERR_FILE_ERROR;
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    input_trace_data_size = (uint32_t)size;
    input_trace_data = new (std::nothrow) uint8_t[input_trace_data_size];
    if (!input_trace_data) {
        fclose(fp);
        return OCSD_ERR_MEM;
    } 
    rewind(fp);
    bytes_read = fread(input_trace_data, 1, input_trace_data_size, fp);
    fclose(fp);
    if (bytes_read < (size_t)input_trace_data_size)
        return OCSD_ERR_FILE_ERROR;

    /* load up a memory image */
    filename = (usr_snapshot_path ? usr_snapshot_path : default_base_snapshot_path);
    filename += (std::string)juno_snapshot;
    filename += (std::string)memory_dump_filename;

    fp = fopen(filename.c_str(), "rb");
    if (!fp)
        return OCSD_ERR_FILE_ERROR;
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    program_image_size = (uint32_t)size;
    program_image_buffer = new (std::nothrow) uint8_t[program_image_size];
    if (!program_image_buffer) {
        fclose(fp);
        return OCSD_ERR_MEM;
    }
    rewind(fp);
    bytes_read = fread(program_image_buffer, 1, program_image_size, fp);
    fclose(fp);
    if (bytes_read < (size_t)program_image_size)
        return OCSD_ERR_FILE_ERROR;
    program_image_address = mem_dump_address;
    return OCSD_OK;
}

static ocsd_err_t createETMv4StreamDecoder()
{
    ocsd_etmv4_cfg trace_config;
    ocsd_err_t err = OCSD_OK;
    EtmV4Config *pCfg = 0;
    
    /*
    * populate the ETMv4 configuration structure with
    * hard coded values from snapshot .ini files.
    */

    trace_config.arch_ver = ARCH_V8;
    trace_config.core_prof = profile_CortexA;

    trace_config.reg_configr = 0x000000C1;
    trace_config.reg_traceidr = 0x00000010;   /* this is the trace ID -> 0x10, change this to analyse other streams in snapshot.*/
    trace_config.reg_idr0 = 0x28000EA1;
    trace_config.reg_idr1 = 0x4100F403;
    trace_config.reg_idr2 = 0x00000488;
    trace_config.reg_idr8 = 0x0;
    trace_config.reg_idr9 = 0x0;
    trace_config.reg_idr10 = 0x0;
    trace_config.reg_idr11 = 0x0;
    trace_config.reg_idr12 = 0x0;
    trace_config.reg_idr13 = 0x0;

    pCfg = new (std::nothrow) EtmV4Config(&trace_config);
    if (!pCfg)
        return OCSD_ERR_MEM;
        
    err = pDecoder->createDecoder(OCSD_BUILTIN_DCD_ETMV4I,  /* etm v4 decoder */
                                   OCSD_CREATE_FLG_FULL_DECODER, /* full trace decode */ 
                                    pCfg);
    delete pCfg;
    return err;
}

/* Create the decode tree and add the error logger,  stream decoder, memory image data to it.
   Also register the output callback that processes the decoded trace packets. */
static ocsd_err_t initialiseDecoder()
{
    ocsd_err_t ret = OCSD_OK;

    /* use the creation function to get the type of decoder we want 
        either OCSD_TRC_SRC_SINGLE : single trace source - not frame formatted
               OCSD_TRC_SRC_FRAME_FORMATTED :multi source - CoreSight trace frame
        and set the config flags for operation
                OCSD_DFRMTR_FRAME_MEM_ALIGN: input data mem aligned -> no syncs

       For this test we create a decode that can unpack frames and is not expecting sync packets.
     */
    pDecoder = DecodeTree::CreateDecodeTree(OCSD_TRC_SRC_FRAME_FORMATTED, OCSD_DFRMTR_FRAME_MEM_ALIGN);
    if (!pDecoder)
        return OCSD_ERR_MEM;

    /* set up decoder logging - the message logger for output, and the error logger for the library */
    logger.setLogOpts(ocsdMsgLogger::OUT_STR_CB); /* no IO from the logger, just a string callback. */
    logger.setStrOutFn(&logCB); /* set the callback - in this example it will go to stdio  but this is up to the implementor. */
    
    // for debugging - stdio and file
//    logger.setLogOpts(ocsdMsgLogger::OUT_FILE | ocsdMsgLogger::OUT_STDOUT);
    
    err_log.initErrorLogger(OCSD_ERR_SEV_INFO);
    err_log.setOutputLogger(&logger); /* pass the output logger to the error logger. */

    pDecoder->setAlternateErrorLogger(&err_log); /* pass the error logger to the decoder, do not use the library version. */

    /* now set up the elements that the decoder needs */

    /* we will decode one of the streams in this example 
       create a Full decode ETMv4 stream decoder */
    ret = createETMv4StreamDecoder();
    if (ret != OCSD_OK)
        return ret;
    
    /* as this has full decode we must supply a memory image. */

    ret = pDecoder->createMemAccMapper();   // the mapper is needed to add code images to.
    if (ret != OCSD_OK)
        return ret;

    if (use_callback)
    {
        // in this example we have a single buffer so we demonstrate how to use a callback.
        // we are passing the buffer pointer as context as we only have one buffer, but this 
        // could be a structure that is a list of memory image buffers. Context is entirely
        // client defined.
        // Always use OCSD_MEM_SPACE_ANY unless there is a reason to restrict the image to a specific
        // memory space.
        pDecoder->addCallbackMemAcc(program_image_address, program_image_address + program_image_size - 1,
            OCSD_MEM_SPACE_ANY, mem_access_callback_fn, program_image_buffer);
    }
    else
    {
        // or we can use the built in memory buffer interface - split our one buffer into two to 
        // demonstrate the addition of multiple regions
        ocsd_vaddr_t block1_st, block2_st;
        uint32_t block1_sz, block2_sz;
        uint8_t* p_block1, * p_block2;

        // break our single buffer into 2 buffers for demo purposes
        block1_sz = program_image_size / 2;
        block1_sz &= ~0x3; // align
        block2_sz = program_image_size - block1_sz;
        block1_st = program_image_address;  // loaded program memory start address of program
        block2_st = program_image_address + block1_sz;
        p_block1 = program_image_buffer;
        p_block2 = program_image_buffer + block1_sz;

        /* how to add 2 "separate" buffers to the decoder */
        // Always use OCSD_MEM_SPACE_ANY unless there is a reason to restrict the image to a specific
        // memory space.
        ret = pDecoder->addBufferMemAcc(block1_st, OCSD_MEM_SPACE_ANY, p_block1, block1_sz);
        if (ret != OCSD_OK)
            return ret;

        ret = pDecoder->addBufferMemAcc(block2_st, OCSD_MEM_SPACE_ANY, p_block2, block2_sz);
        if (ret != OCSD_OK)
            return ret;
    }

    /* finally we need to provide an output callback to recieve the decoded information */
    pDecoder->setGenTraceElemOutI(&output);
    return ret;
}

/* get rid of the objects we created */
static void destroyDecoder()
{
    delete pDecoder;
    delete [] input_trace_data;
    delete [] program_image_buffer;
}


/* if we set up to use a callback to access memory image then this is what will be called. */
/* In this case the client must do all the work in determining if the requested address is in the 
   memory area. */
uint32_t  mem_access_callback_fn(const void *p_context, const ocsd_vaddr_t address, 
    const ocsd_mem_space_acc_t mem_space, const uint32_t reqBytes, uint8_t *byteBuffer)
{
    ocsd_vaddr_t buf_end_address = program_image_address + program_image_size - 1;
    uint32_t read_bytes = reqBytes;
    
    /* context should be our memory image buffer - if not return 0 bytes read */
    if (p_context != program_image_buffer)
        return 0;

    /* not concerned with memory spaces - assume all global */
    if ((address < program_image_address) || (address > buf_end_address))
        return 0;   // requested address not in our buffer.

    // if requested bytes from address more than we have, only read to end of buffer
    if ((address + reqBytes - 1) > buf_end_address)
        read_bytes = (uint32_t)(buf_end_address - (address - 1));

    // copy the requested data.
    memcpy(byteBuffer, program_image_buffer + (address - program_image_address), read_bytes);

    return read_bytes;
}

/* use the decoder to process the global trace data buffer */
static ocsd_datapath_resp_t processTraceData(uint32_t *bytes_done)
{
    /* process in blocks of fixed size. */
    #define DATA_CHUNK_SIZE 2048

    ocsd_datapath_resp_t resp = OCSD_RESP_CONT;
    uint32_t block_size, buff_offset, bytes_to_do = input_trace_data_size, bytes_processed;
    ocsd_trc_index_t index = 0;

    /* process the data in chunks, until either all done or
    * error occurs.
    */
    while ((resp == OCSD_RESP_CONT) && (bytes_to_do))
    {
        /* size up a block of input data */
        block_size = (bytes_to_do >= DATA_CHUNK_SIZE) ? DATA_CHUNK_SIZE : bytes_to_do;
        buff_offset = input_trace_data_size - bytes_to_do;

        /* push it through the decoder */
        resp = pDecoder->TraceDataIn(OCSD_OP_DATA, index, block_size, 
            input_trace_data + buff_offset, &bytes_processed);

        /* adjust counter per bytes processed */
        bytes_to_do -= bytes_processed;
        index += bytes_processed;
    }

    /* if all done then signal end of trace - flushes out any remaining data */
    if (!bytes_to_do)
        resp = pDecoder->TraceDataIn(OCSD_OP_EOT, 0, 0, 0, 0);

    /* return amount processed */
    *bytes_done = input_trace_data_size - bytes_to_do;
    return resp;
}

void printhelp()
{
    printf("mem_buff_demo memory accessor test program\n");
    printf("==========================================\n\n");
    printf("options:-\n");
    printf("    -callback       : test callback mem acc functionality\n");
    printf("    -logfile        : output test result to logfile\n");
    printf("    -noprint        : no output to console\n");
    printf("    -ss_path <path> : set path to snapshot directory\n");
    printf("\n\n========================================\n\n");
    
}

int process_cmd_line_opts(int argc, char* argv[])
{
    int options_to_process = argc - 1;
    int opt_idx = 1;
    std::string opt;

    while(options_to_process)
    { 
        opt = argv[opt_idx];

        if (opt == "-callback")
            use_callback = true;
        else if (opt == "-logfile")
            use_logfile = true;
        else if (opt == "-noprint")
            no_print = true;
        else if (opt == "-ss_path")
        {
            opt_idx++;
            options_to_process--;
            
            if ((opt_idx >= argc) || (strlen(argv[opt_idx]) == 0))
            {
                printf("-ss_path: Missing path parameter or zero length\n");
                return -1;
            }
            else
            {
                if (strlen(argv[opt_idx]) > (MAX_TRACE_FILE_PATH_LEN - 32))
                {
                    printf("-ss_path: path too long\n");
                    return -1;
                }
                usr_snapshot_path = argv[opt_idx];
            }
        }
        else
        {
            printhelp();
            return -1;
        }
        opt_idx++;
        options_to_process--;
    }
    return 0;
}

void log_cmd_line_opts(int argc, char* argv[])
{
    std::ostringstream oss;
    oss << "Test Command Line:-\n";
    oss << argv[0] << "   ";
    for (int i = 1; i < argc; i++)
    {
        oss << argv[i] << "  ";
    }
    oss << "\n\n";
    logCB.printOutStr(oss.str());
}

/* main routine - init input data, decode, finish ... */
int main(int argc, char* argv[])
{
    int ret = OCSD_OK;
    ocsd_datapath_resp_t retd;
    char msg[256];
    uint32_t bytes_done;

    if (process_cmd_line_opts(argc, argv) != 0)
        return -1;

    if (use_logfile)
        logCB.setOutFile(use_callback ? "mem_buff_demo_cb.ppl" : "mem_buff_demo.ppl");
    if (no_print)
        logCB.setOutConsole(false);

    logCB.printOutStr("MemBuffDemo\n--------------\n\n");
    log_cmd_line_opts(argc, argv);


    /* initialise all the data needed for decode */
    if ((ret = initDataBuffers()) != OCSD_OK)
    {
        logger.LogMsg("Failed to create trace data buffers\n");
        return ret;
    }
    /* initialise a decoder object */
    if ((ret = initialiseDecoder()) == OCSD_OK)
    {
            retd = processTraceData(&bytes_done);
            if (!OCSD_DATA_RESP_IS_CONT(retd))
            {
                ret = OCSD_ERR_DATA_DECODE_FATAL;
                logger.LogMsg("Processing failed with data error\n");
            }

        /* get rid of the decoder and print a brief result. */
        destroyDecoder();
        sprintf(msg, "Processed %u bytes out of %u\n", bytes_done, input_trace_data_size);
        logger.LogMsg(msg);
    }
    else
        logger.LogMsg("Failed to create decoder for trace processing\n");
    return ret;
}
