/*
 * File:    DataSourceImager.cpp
 *
 * Copyright (c) Freescale Semiconductor, Inc. All rights reserved.
 * See included license file for license details.
 */

#include "blfwk/DataSourceImager.h"
#include <stdlib.h>
#include <string.h>

using namespace blfwk;

DataSourceImager::DataSourceImager()
    : Blob()
    , m_fill(0)
    , m_baseAddress(0)
    , m_isBaseAddressSet(false)
{
}

void DataSourceImager::setBaseAddress(uint32_t address)
{
    m_baseAddress = address;
    m_isBaseAddressSet = true;
}

void DataSourceImager::setFillPattern(uint8_t pattern)
{
    m_fill = pattern;
}

void DataSourceImager::reset()
{
    clear();

    m_fill = 0;
    m_baseAddress = 0;
    m_isBaseAddressSet = false;
}

//! \param dataSource Pointer to an instance of a concrete data source subclass.
//!
void DataSourceImager::addDataSource(DataSource *source)
{
    unsigned segmentCount = source->getSegmentCount();
    unsigned index = 0;
    for (; index < segmentCount; ++index)
    {
        addDataSegment(source->getSegmentAt(index));
    }
}

//! \param segment The segment to add. May be any type of data segment, including
//!     a pattern segment.
void DataSourceImager::addDataSegment(DataSource::Segment *segment)
{
    DataSource::PatternSegment *patternSegment = dynamic_cast<DataSource::PatternSegment *>(segment);

    unsigned segmentLength = segment->getLength();
    bool segmentHasLocation = segment->hasNaturalLocation();
    uint32_t segmentAddress = segment->getBaseAddress();

    uint8_t *toPtr = NULL;
    unsigned addressDelta;
    unsigned newLength;

    // If a pattern segment's length is 0 then make it as big as the fill pattern.
    // This needs to be done before the buffer is adjusted.
    if (patternSegment && segmentLength == 0)
    {
        SizedIntegerValue &pattern = patternSegment->getPattern();
        segmentLength = pattern.getSize();
    }

    if (segmentLength)
    {
        if (segmentHasLocation)
        {
            // Make sure a base address is set.
            if (!m_isBaseAddressSet)
            {
                m_baseAddress = segmentAddress;
                m_isBaseAddressSet = true;
            }

            // The segment is located before our buffer's first address.
            // toPtr is not set in this if, but in the else branch of the next if.
            // Unless the segment completely overwrite the current data.
            if (segmentAddress < m_baseAddress)
            {
                addressDelta = m_baseAddress - segmentAddress;

                uint8_t *newData = (uint8_t *)malloc(m_length + addressDelta);
                memcpy(&newData[addressDelta], m_data, m_length);
                free(m_data);

                m_data = newData;
                m_length += addressDelta;
                m_baseAddress = segmentAddress;
            }

            // This segment is located or extends outside of our buffer.
            if (segmentAddress + segmentLength > m_baseAddress + m_length)
            {
                newLength = segmentAddress + segmentLength - m_baseAddress;

                m_data = (uint8_t *)realloc(m_data, newLength);

                // Clear any bytes between the old data and the new segment.
                addressDelta = segmentAddress - (m_baseAddress + m_length);
                if (addressDelta)
                {
                    memset(m_data + m_length, 0, addressDelta);
                }

                toPtr = m_data + (segmentAddress - m_baseAddress);
                m_length = newLength;
            }
            else
            {
                toPtr = m_data + (segmentAddress - m_baseAddress);
            }
        }
        // Segment has no natural location, so just append it to the end of our buffer.
        else
        {
            newLength = m_length + segmentLength;
            m_data = (uint8_t *)realloc(m_data, newLength);
            toPtr = m_data + m_length;
            m_length = newLength;
        }
    }

    // A loop is used because getData() may fill in less than the requested
    // number of bytes per call.
    unsigned offset = 0;
    while (offset < segmentLength)
    {
        offset += segment->getData(offset, segmentLength - offset, toPtr + offset);
    }
}
