/**
 * @file
 *
 * @copyright
 * @verbatim
   Copyright @ 2017 Audi Electronics Venture GmbH. All rights reserved.

       This Source Code Form is subject to the terms of the Mozilla
       Public License, v. 2.0. If a copy of the MPL was not distributed
       with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

   If it is not possible or desirable to put the notice in a particular file, then
   You may include the notice in a location (such as a LICENSE file in a
   relevant directory) where a recipient would be likely to look for such a notice.

   You may add additional accurate notices of copyright ownership.
   @endverbatim
*/

#include "target.h"

#include <memory>   //std::unique_ptr<>
#include "a_util/result/error_def.h"
#include "legacy_error_macros.h"

#include "codec/access_element.h"

#include "mapping/configuration/map_configuration.h"

namespace mapping
{
namespace rt
{
    //define all needed error types and values locally
    _MAKE_RESULT(-3, ERR_UNEXPECTED)
    _MAKE_RESULT(-4, ERR_POINTER)
    _MAKE_RESULT(-12, ERR_MEMORY)
}
}

using namespace mapping;
using namespace mapping::rt;

Target::Target(IMappingEnvironment& oEnv) :
    _counter(0), _env(oEnv)
{
}

Target::~Target()
{
    for (TargetElementList::iterator it = _target_elements.begin();
        it != _target_elements.end(); ++it)
    {
        delete *it;
    }
    _target_elements.clear();
    _simulation_time_elements.clear();
}

a_util::result::Result Target::create(const oo::MapConfiguration& oMapConfig,
    const oo::MapTarget& oMapTarget, const std::string& strTargetDescription, 
    SourceMap& oSources)
{
    _name = oMapTarget.getName();
    _type = oMapTarget.getType();
    ddl::CodecFactory oFactory(_type.c_str(), strTargetDescription.c_str());
    RETURN_IF_FAILED(oFactory.isValid());
    
    // Alloc and zero memory
    _buffer.resize(oFactory.getStaticBufferSize(), 0);

    // Begin here, end when target is destroyed or after reset
    _codec.reset(new ddl::StaticCodec(oFactory.makeStaticCodecFor(&_buffer[0], _buffer.size())));

    // Get structure from DDL
    const ddl::DDLDescription* pDescription = oMapConfig.getDescription();
    if (!pDescription) { return ERR_POINTER; }
    const ddl::DDLComplex* pStruct = pDescription->getStructByName(_type);
    if (!pStruct) { return ERR_POINTER; }

    // Create element for each assignment in mapping configuration
    const oo::MapAssignmentList& oAssignmentList = oMapTarget.getAssignmentList();
    for(oo::MapAssignmentList::const_iterator itAssignment = oAssignmentList.begin();
        itAssignment != oAssignmentList.end(); ++itAssignment)
    {
        const ddl::DDLElement* pElem = NULL;
        bool bIsArrayElement = false;
        RETURN_IF_FAILED(DDLHelper::LookupElement(*pStruct, itAssignment->getTo(),
            pElem, bIsArrayElement));

        unsigned int szElement = pElem->getArraysize();
        if(bIsArrayElement)
        {
            szElement = 1;
        }

        std::string strPath = itAssignment->getTo();
        if (szElement > 1)
        {
            strPath.append("[0]");
        }

        ddl::DDLComplex* pComplex = dynamic_cast<ddl::DDLComplex*>(pElem->getTypeObject());
        if (pComplex)
        {
            // No StructElement is created for the actual struct, only for its elements. Therefore, if
            // the assignment is a struct, we need to amend the path by the first element of the struct
            if (pComplex->getElements().empty()) return ERR_UNEXPECTED;
            strPath.push_back('.');
            strPath.append(pComplex->getElements()[0]->getName());
        }

        size_t nIdx = 0;
        RETURN_IF_FAILED(ddl::access_element::find_index(*_codec, strPath, nIdx));
        void* pTargetElementPtr = _codec->getElementAddress(nIdx);

        // Create target element
        std::unique_ptr<TargetElement> pElement(new TargetElement(this));
        RETURN_IF_FAILED(pElement->create(pTargetElementPtr, pElem->getTypeObject(),
            szElement, itAssignment->getTo()));

        // If a constant is given, there can be no transformation
        if(!itAssignment->getConstant().empty())
        {                   
            _constant_elements.push_back(make_pair(itAssignment->getConstant(), pElement.get()));
            
        }
        else if(!itAssignment->getFunction().empty())
        {
            if(itAssignment->getFunction() == "simulation_time")
            {
                _simulation_time_elements.push_back(pElement.get());
            }
            else if(itAssignment->getFunction() == "trigger_counter")
            {
                uint64_t nMod = 0;
                if (!itAssignment->getModulo().empty())
                {
                    nMod = a_util::strings::toUInt64(itAssignment->getModulo());
                }
                _counter_elements.push_back(std::make_pair(nMod, pElement.get()));
            }
            else if(itAssignment->getFunction() == "received")
            {
                // Add assignments to source
                SourceMap::iterator itSource = oSources.find(itAssignment->getSource());
                if (itSource == oSources.end())
                {
                    return ERR_UNEXPECTED;
                }

                RETURN_IF_FAILED(itSource->second->addAssignment(oMapConfig, "received()", pElement.get()));
            }

        }
        else
        {
            // Add Transformation for Mapping configuration
            const std::string& strTransformation = itAssignment->getTransformation();
            if(!strTransformation.empty())
            {
                RETURN_IF_FAILED(pElement->setTransformation(
                    oMapConfig.getTransformation(strTransformation)));
            }                   

            // Add assignments to source
            SourceMap::iterator itSource = oSources.find(itAssignment->getSource());
            if (itSource == oSources.end())
            {
                return ERR_UNEXPECTED;
            }

            // getFrom() is empty when the source itself is referenced
            RETURN_IF_FAILED(itSource->second->addAssignment(oMapConfig, itAssignment->getFrom(), pElement.get()));
        }

        _target_elements.push_back(pElement.release());
    }

    // initialize memory
    RETURN_IF_FAILED(reset(oMapConfig));

    return a_util::result::SUCCESS;
}

a_util::result::Result Target::reset(const oo::MapConfiguration& oMapConfig)
{
    a_util::result::Result nResult = a_util::result::SUCCESS;
    // Reset Counter
    _counter = 0;

    // Set default values from DDL
    const ddl::DDLDescription* pDescription = oMapConfig.getDescription();
    if (!pDescription) { return ERR_POINTER; }
    const ddl::DDLComplex* pStruct = pDescription->getStructByName(_type);
    if (!pStruct) { return ERR_POINTER; }
    
    // Set default values from DDL in a newly created data sample
    for (size_t nIdx = 0; nIdx < _codec->getElementCount(); ++nIdx)
    {
        const ddl::StructElement* pElement = NULL;
        if (isFailed(_codec->getElement(nIdx, pElement)))
        {
            continue;
        }

        const ddl::DDLElement* pElem = NULL;
        bool bIsArrayElement = false;
        a_util::result::Result nCurrentResult = DDLHelper::LookupElement(*pStruct,
            pElement->name, pElem, bIsArrayElement);

        unsigned int nLastElementIndex = 1;
        std::string strDefaultValue;
        if (isOk(nCurrentResult))
        {
            nLastElementIndex = pElem->getArraysize();
            // Element Description was found ... check for a default value
            if (pElem->isDefaultValid())
            { 
                strDefaultValue = pElem->getDefaultValue();             
            }           
        }

        // Set error if no error happend so far
        if (isOk(nResult))
        {
            nResult = nCurrentResult;
        }

        if (strDefaultValue.empty())
        {
            // No default detected, just reset this entry
            ddl::access_element::reset(*_codec, pElement->name);
        }
        else
        {
            a_util::variant::Variant var;
            switch (pElement->type)
            {
            case a_util::variant::VT_Bool:
                var.reset(a_util::strings::toBool(strDefaultValue));
                break;
            case a_util::variant::VT_Int8:
                var.reset(a_util::strings::toInt8(strDefaultValue));
                break;
            case a_util::variant::VT_UInt8:
                var.reset(a_util::strings::toUInt8(strDefaultValue));
                break;
            case a_util::variant::VT_Int16:
                var.reset(a_util::strings::toInt16(strDefaultValue));
                break;
            case a_util::variant::VT_UInt16:
                var.reset(a_util::strings::toUInt16(strDefaultValue));
                break;
            case a_util::variant::VT_Int32:
                var.reset(a_util::strings::toInt32(strDefaultValue));
                break;
            case a_util::variant::VT_UInt32:
                var.reset(a_util::strings::toUInt32(strDefaultValue));
                break;
            case a_util::variant::VT_Int64:
                var.reset(a_util::strings::toInt64(strDefaultValue));
                break;
            case a_util::variant::VT_UInt64:
                var.reset(a_util::strings::toUInt64(strDefaultValue));
                break;
            case a_util::variant::VT_Float:
                var.reset(a_util::strings::toFloat(strDefaultValue));
                break;
            case a_util::variant::VT_Double:
                var.reset(a_util::strings::toDouble(strDefaultValue));
                break;
            default:
                ddl::access_element::reset(*_codec, pElement->name);
                break;
            }

            if (!var.isEmpty())
            {
                nCurrentResult = _codec->setElementValue(nIdx, var);
            }
            
            // Set error if no error happend so far
            if (isOk(nResult))
            {
                nResult = nCurrentResult;
            }
        }
    }

    // Set constant elements
    if (isOk(nResult))
    {
        for(Constants::iterator it = _constant_elements.begin(); it != _constant_elements.end(); it++)
        {
            nResult = it->second->setDefaultValue(it->first);
        }
    }
    return nResult;
}

const std::string& Target::getName() const
{
    return _name;
}

const std::string& Target::getType() const
{
    return _type;
}

const TargetElementList& Target::getElementList() const
{
    return _target_elements;
}

size_t Target::getSize() const
{
    return _buffer.size();
}

a_util::result::Result Target::updateAccessFunctionValues()
{
    // set simulation time for simulation_time assignments    
    timestamp_t tmTime = _env.getTime();
    for(std::vector<TargetElement*>::const_iterator it = _simulation_time_elements.begin();
        it != _simulation_time_elements.end(); it++)
    {
        (*it)->setValue(&tmTime, e_int64, sizeof(timestamp_t));
    }

    return a_util::result::SUCCESS;
}

a_util::result::Result Target::updateTriggerFunctionValues()
{
    // increment counter
    _counter++;

    // write counter into trigger_counter assignments
    for(TriggerCounters::const_iterator it = _counter_elements.begin(); it != _counter_elements.end(); it++)
    {
        uint64_t nValue = _counter;
        if (it->first != 0)
        {
            nValue %= it->first;
        }
        it->second->setValue(&nValue, e_uint64, sizeof(nValue));
    }

    return a_util::result::SUCCESS;
}

a_util::result::Result Target::getCurrentBuffer(void* pTargetBuffer, size_t szTargetBuffer)
{
    if(szTargetBuffer < _buffer.size())
    {
        return ERR_MEMORY;
    }

    updateAccessFunctionValues();

    aquireReadLock();
    a_util::memory::copy(pTargetBuffer, szTargetBuffer, &_buffer[0], _buffer.size());
    releaseReadLock();
    return a_util::result::SUCCESS;
}

a_util::result::Result Target::getBufferRef(const void*& pBuffer, size_t& szTargetBuffer)
{
    updateAccessFunctionValues();

    // synchronization is done by the caller!
    pBuffer = &_buffer[0];
    szTargetBuffer = _buffer.size();

    return a_util::result::SUCCESS;
}