/*
 * \file       trc_mem_acc_file.cpp
 * \brief      OpenCSD : 
 * 
 * \copyright  Copyright (c) 2015, 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. 
 */ 

#include "mem_acc/trc_mem_acc_file.h"

#include <sstream>
#include <iomanip>

/***************************************************/
/* protected construction and reference counting   */
/***************************************************/

TrcMemAccessorFile::TrcMemAccessorFile() : TrcMemAccessorBase(MEMACC_FILE)
{
    m_ref_count = 0;
    m_base_range_set = false;
    m_has_access_regions = false;
    m_file_size = 0;
}

TrcMemAccessorFile::~TrcMemAccessorFile()
{
    if(m_mem_file.is_open())
        m_mem_file.close();
    if(m_access_regions.size())
    {
        std::list<FileRegionMemAccessor *>::iterator it;
        it = m_access_regions.begin();
        while(it != m_access_regions.end())
        {
            delete (*it);
            it++;
        }
        m_access_regions.clear();
    }
}

ocsd_err_t TrcMemAccessorFile::initAccessor(const std::string &pathToFile, ocsd_vaddr_t startAddr, size_t offset, size_t size)
{
    ocsd_err_t err = OCSD_OK;
    bool init = false;

    m_mem_file.open(pathToFile.c_str(), std::ifstream::binary | std::ifstream::ate);
    if(m_mem_file.is_open())
    {
        m_file_size = (ocsd_vaddr_t)m_mem_file.tellg() & ((ocsd_vaddr_t)~0x1);
        m_mem_file.seekg(0, m_mem_file.beg);
        // adding an offset of 0, sets the base range.
        if((offset == 0) && (size == 0))
        {
            init = AddOffsetRange(startAddr, ((size_t)m_file_size)-offset, offset);
        }
        else if((offset + size) <= m_file_size)
        {
            // if offset != 0, size must by != 0
            init = AddOffsetRange(startAddr, size, offset);
        }
        m_file_path = pathToFile;
    }
    else 
        err = OCSD_ERR_MEM_ACC_FILE_NOT_FOUND;
    if(!init)
        err = OCSD_ERR_NOT_INIT;
    return err;
}


FileRegionMemAccessor *TrcMemAccessorFile::getRegionForAddress(const ocsd_vaddr_t startAddr) const
{
    FileRegionMemAccessor *p_region = 0;
    if(m_has_access_regions)
    {
        std::list<FileRegionMemAccessor *>::const_iterator it;
        it = m_access_regions.begin();
        while((it != m_access_regions.end()) && (p_region == 0))
        {
            if((*it)->addrInRange(startAddr))
                p_region = *it;
            it++;
        }
    }
    return p_region;
}


/***************************************************/
/* static object creation                          */
/***************************************************/

std::map<std::string, TrcMemAccessorFile *> TrcMemAccessorFile::s_FileAccessorMap;

// return existing or create new accessor
ocsd_err_t TrcMemAccessorFile::createFileAccessor(TrcMemAccessorFile **p_acc, const std::string &pathToFile, ocsd_vaddr_t startAddr, size_t offset /*= 0*/, size_t size /*= 0*/)
{
    ocsd_err_t err = OCSD_OK;
    TrcMemAccessorFile * acc = 0;
    std::map<std::string, TrcMemAccessorFile *>::iterator it = s_FileAccessorMap.find(pathToFile);
    if(it != s_FileAccessorMap.end())
    {
        acc = it->second;
        if(acc->addrStartOfRange(startAddr))
            acc->IncRefCount();
        else
        {
            err = OCSD_ERR_MEM_ACC_FILE_DIFF_RANGE;
            acc = 0;
        }
    }
    else
    { 
        acc = new (std::nothrow) TrcMemAccessorFile();
        if(acc != 0)
        {
            if((err = acc->initAccessor(pathToFile,startAddr, offset,size)) == OCSD_OK)
            {
                acc->IncRefCount();
                s_FileAccessorMap.insert(std::pair<std::string, TrcMemAccessorFile *>(pathToFile,acc));
            }
            else
            {
                delete acc;
                acc = 0;
            }
        }
        else
            err = OCSD_ERR_MEM;
    }
    *p_acc = acc;
    return err;
}

void TrcMemAccessorFile::destroyFileAccessor(TrcMemAccessorFile *p_accessor)
{
    if(p_accessor != 0)
    {
        p_accessor->DecRefCount();
        if(p_accessor->getRefCount() == 0)
        {
            std::map<std::string, TrcMemAccessorFile *>::iterator it = s_FileAccessorMap.find(p_accessor->getFilePath());
            if(it != s_FileAccessorMap.end())
            {
                s_FileAccessorMap.erase(it);
            }
            delete p_accessor;
        }
    }
}

const bool TrcMemAccessorFile::isExistingFileAccessor(const std::string &pathToFile)
{
    bool bExists = false;
    std::map<std::string, TrcMemAccessorFile *>::const_iterator it = s_FileAccessorMap.find(pathToFile);
    if(it != s_FileAccessorMap.end())
        bExists = true;
    return bExists;
}

TrcMemAccessorFile * TrcMemAccessorFile::getExistingFileAccessor(const std::string &pathToFile)
{
    TrcMemAccessorFile * p_acc = 0;
    std::map<std::string, TrcMemAccessorFile *>::iterator it = s_FileAccessorMap.find(pathToFile);
    if(it != s_FileAccessorMap.end())
        p_acc = it->second;
    return p_acc;
}



/***************************************************/
/* accessor instance functions                     */
/***************************************************/
const uint32_t TrcMemAccessorFile::readBytes(const ocsd_vaddr_t address, const ocsd_mem_space_acc_t mem_space, const uint8_t trcID, const uint32_t reqBytes, uint8_t *byteBuffer)
{
    if(!m_mem_file.is_open())
        return 0;
    uint32_t bytesRead = 0;

    if(m_base_range_set)
    {
        bytesRead = TrcMemAccessorBase::bytesInRange(address,reqBytes);    // get avialable bytes in range.
        if(bytesRead)
        {
            ocsd_vaddr_t addr_pos = (ocsd_vaddr_t)m_mem_file.tellg();
            if((address - m_startAddress) != addr_pos)
                m_mem_file.seekg(address - m_startAddress);
            m_mem_file.read((char *)byteBuffer,bytesRead);
        }
    }

    if((bytesRead == 0) && m_has_access_regions)
    {
        bytesRead = bytesInRange(address,reqBytes);
        if(bytesRead)
        {
            FileRegionMemAccessor *p_region = getRegionForAddress(address);
            ocsd_vaddr_t addr_pos = (ocsd_vaddr_t)m_mem_file.tellg();
            if((address - p_region->regionStartAddress() + p_region->getOffset()) != addr_pos)
                m_mem_file.seekg(address - p_region->regionStartAddress() + p_region->getOffset());
             m_mem_file.read((char *)byteBuffer,bytesRead);
        }
    }
    return bytesRead;
}

bool TrcMemAccessorFile::AddOffsetRange(const ocsd_vaddr_t startAddr, const size_t size, const size_t offset)
{
    bool addOK = false;
    if(m_file_size == 0)    // must have set the file size
        return false;
    if(addrInRange(startAddr) || addrInRange(startAddr+size-1))  // cannot be overlapping
        return false;

    // now either set the base range or an offset range
    if(offset == 0)
    {
        if(!m_base_range_set)
        {
            setRange(startAddr, startAddr+size-1);
            m_base_range_set = true;
            addOK = true;
        }
    }
    else
    {
        if((offset + size) <= m_file_size)
        {
            FileRegionMemAccessor *frmacc = new (std::nothrow) FileRegionMemAccessor();
            if(frmacc)
            {
                frmacc->setOffset(offset);
                frmacc->setRange(startAddr,startAddr+size-1);
                m_access_regions.push_back(frmacc);
                m_access_regions.sort();
                // may need to trim the 0 offset base range...
                if(m_base_range_set)
                {
                    std::list<FileRegionMemAccessor *>::iterator it;
                    it = m_access_regions.begin();
                    size_t first_range_offset = (*it)->getOffset();
                    if((m_startAddress + first_range_offset - 1) > m_endAddress)
                        m_endAddress = m_startAddress + first_range_offset - 1;
                }
                addOK = true;
                m_has_access_regions = true;
            }        
        }
    }
    return addOK;
}

const bool TrcMemAccessorFile::addrInRange(const ocsd_vaddr_t s_address) const
{
    bool bInRange = false;
    if(m_base_range_set)
        bInRange = TrcMemAccessorBase::addrInRange(s_address);

    if(!bInRange && m_has_access_regions)
    {
        if(getRegionForAddress(s_address) != 0)
            bInRange = true;
    }
    return bInRange;
}

const bool TrcMemAccessorFile::addrStartOfRange(const ocsd_vaddr_t s_address) const
{
    bool bInRange = false;
    if(m_base_range_set)
        bInRange = TrcMemAccessorBase::addrStartOfRange(s_address);
    if(!bInRange && m_has_access_regions)
    {
        FileRegionMemAccessor *pRegion = getRegionForAddress(s_address);
        if(pRegion)
            bInRange = (pRegion->regionStartAddress() == s_address);
    }
    return bInRange;
}


    /* validate ranges */
const bool TrcMemAccessorFile::validateRange()
{
    bool bRangeValid = true;
    if(m_base_range_set)
        bRangeValid = TrcMemAccessorBase::validateRange();

    if(m_has_access_regions && bRangeValid)
    {
        std::list<FileRegionMemAccessor *>::const_iterator it;
        it = m_access_regions.begin();
        while((it != m_access_regions.end()) && bRangeValid)
        {
            bRangeValid = (*it)->validateRange();
            it++;
        }
    }
    return bRangeValid;
}

const uint32_t TrcMemAccessorFile::bytesInRange(const ocsd_vaddr_t s_address, const uint32_t reqBytes) const
{
    uint32_t bytesInRange = 0;
    if(m_base_range_set)
        bytesInRange = TrcMemAccessorBase::bytesInRange(s_address,reqBytes);

    if((bytesInRange == 0) && (m_has_access_regions))
    {
        FileRegionMemAccessor *p_region = getRegionForAddress(s_address);
        bytesInRange = p_region->bytesInRange(s_address,reqBytes);
    }

    return bytesInRange;
}
    
const bool TrcMemAccessorFile::overLapRange(const TrcMemAccessorBase *p_test_acc) const
{
    bool bOverLapRange = false;
    if(m_base_range_set)
        bOverLapRange = TrcMemAccessorBase::overLapRange(p_test_acc);

    if(!bOverLapRange && (m_has_access_regions))
    {
        std::list<FileRegionMemAccessor *>::const_iterator it;
        it = m_access_regions.begin();
        while((it != m_access_regions.end()) && !bOverLapRange)
        {
            bOverLapRange = (*it)->overLapRange(p_test_acc);
            it++;
        }
    }
    return bOverLapRange;
}

    /*! Override to handle ranges and offset accessors plus add in file name. */
void TrcMemAccessorFile::getMemAccString(std::string &accStr) const
{
    std::ostringstream oss;
    accStr = "";
    if(m_base_range_set)
    {
        TrcMemAccessorBase::getMemAccString(accStr);
    }

    if(m_has_access_regions)
    {
        std::string addStr;
        std::list<FileRegionMemAccessor *>::const_iterator it;
        it = m_access_regions.begin();
        while(it != m_access_regions.end())
        {
            (*it)->getMemAccString(addStr);
            if(accStr.length())
                accStr += "\n";
            accStr += addStr;
            it++;
        }
    }
    accStr += (std::string)"\nFilename=" + m_file_path;
}

/* End of File trc_mem_acc_file.cpp */
