/*
 * Copyright (c) 2019-2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.

 *
 */
#include "mlxreg_parser.h"

#include <sstream>
#include <iostream>
#include <common/bit_slice.h>
#include <common/tools_utils.h>
#include <errno.h>
#include <adb_parser/adb_instance.h>
#include <mtcr.h>

using namespace mlxreg;

/************************************
 * Function: RegParser
 ************************************/
template<bool dynamic>
_RegAccessParser_impl<dynamic>::_RegAccessParser_impl(string data,
                                                      string indexes,
                                                      string ops,
                                                      Adb* adb,
                                                      AdbInstance* regNode,
                                                      std::vector<u_int32_t> buffer,
                                                      bool ignore_ro,
                                                      bool full_path)
{
    _data = data;
    _indexes = indexes;
    _ops = ops;
    _adb = adb;
    _regNode = regNode;
    _ignore_ro = ignore_ro;
    _full_path = full_path;
    output_file = "";
    if (!regNode)
    {
        _parseMode = Pm_Unknown;
    }
    else
    {
        _parseMode = Pm_Known;
    }
    _len = buffer.size();
    _buffer = buffer;
    /*
     * Initiate byte-swapping
     * At this point we know the the buffer <buffer> argument already been trough byte-
     * swapping process, we need to re-commit this process so the data will be correct.
     */
    for (typename std::vector<u_int32_t>::size_type j = 0; j < _buffer.size(); j++)
    {
        _buffer[j] = CPU_TO_BE32((_buffer[j]));
    }
}
/************************************
 * Function: RegParser
 ************************************/
template<bool dynamic>
_RegAccessParser_impl<dynamic>::_RegAccessParser_impl(string data,
                                                      string indexes,
                                                      string ops,
                                                      Adb* adb,
                                                      AdbInstance* regNode,
                                                      u_int32_t len,
                                                      bool ignore_ro,
                                                      bool full_path)
{
    _data = data;
    _indexes = indexes;
    _ops = ops;
    _adb = adb;
    _regNode = regNode;
    _ignore_ro = ignore_ro;
    _full_path = full_path;
    output_file = "";
    _len = len;
    // Set parsing method
    if (!regNode)
    {
        if (_len > MAX_REG_SIZE)
        {
            throw MlxRegException("Register length: 0x%08x is too large", _len);
        }
        _parseMode = Pm_Unknown;
        _len = (_len + 3) / 4;
    }
    else
    {
        _parseMode = Pm_Known;

        // The interface of the constructor expects len in bytes.
        // The size of _regNode is in bits.
        // However internally it should hold it in dwords for consistecy with the definition of _buffer, which is a
        // vector of dwords. So when using the passed len argument it should be divided by 4, but in the case where the
        // buffer is defined by the size of the register, we need to divide by 32.
        _len = _regNode->containsDynamicArray() ? len >> 2 : (_regNode->get_size()) >> 5;
    }
    // Resize buffer
    _buffer.resize(_len);
}

/************************************
 * Function: parse
 ************************************/
template<bool dynamic>
std::vector<u_int32_t> _RegAccessParser_impl<dynamic>::genBuff()
{
    if (_parseMode == Pm_Known)
    {
        return genBuffKnown();
    }
    else
    {
        return genBuffUnknown();
    }
}

/************************************
 * Function: genBuffKnown
 ************************************/
template<bool dynamic>
std::vector<u_int32_t> _RegAccessParser_impl<dynamic>::genBuffKnown()
{
    parse_register_params(_indexes, ParamType::INDEX);
    parse_register_params(_ops, ParamType::OP);
    parse_register_params(_data, ParamType::DATA);

    _adb->traverse_layout(_regNode,
                          "",
                          0,
                          (uint8_t*)&(_buffer[0]),
                          _buffer.size() * sizeof(uint32_t),
                          _RegAccessParser_impl<dynamic>::_on_traverse_update_buffer,
                          (void*)this,
                          false,
                          false,
                          _full_path);
    if (_params_map.size() > 0)
    {
        string unfound_name = "";
        string unfound_index_name = "";
        string unfound_op_name = "";
        for (const auto& entry : _params_map)
        {
            if (entry.second.type == ParamType::INDEX)
            {
                unfound_index_name = entry.first;
                break;
            }
            else if (entry.second.type == ParamType::OP)
            {
                unfound_op_name = entry.first;
            }
            else if (entry.second.type == ParamType::DATA)
            {
                unfound_name = entry.first;
            }
        }
        if (!unfound_index_name.empty())
        {
            throw MlxRegException("Can't find INDEX name: \"%s\"", unfound_index_name.c_str());
        }
        if (!unfound_op_name.empty())
        {
            throw MlxRegException("Can't find OP name: \"%s\"", unfound_op_name.c_str());
        }
        if (!unfound_name.empty())
        {
            throw MlxRegException("Can't find field name: \"%s\"", unfound_name.c_str());
        }
    }
    // Convert to BE32
    for (typename std::vector<u_int32_t>::size_type j = 0; j < _buffer.size(); j++)
    {
        _buffer[j] = CPU_TO_BE32((_buffer[j]));
    }
    return _buffer;
}

/************************************
 * Function: genBuffUnknown
 ************************************/
template<bool dynamic>
std::vector<u_int32_t> _RegAccessParser_impl<dynamic>::genBuffUnknown()
{
    parseUnknown();
    // Convert to BE32
    for (typename std::vector<u_int32_t>::size_type j = 0; j < _buffer.size(); j++)
    {
        _buffer[j] = CPU_TO_BE32((_buffer[j]));
    }
    return _buffer;
}

/************************************
 * Function: parseIndexes
 ************************************/
template<bool dynamic>
bool _RegAccessParser_impl<dynamic>::_on_traverse_update_buffer(const string& calculated_path,
                                                                uint64_t calculated_offset,
                                                                uint64_t calculated_value,
                                                                AdbInstance* instance,
                                                                void* context)
{
    (void)context;
    (void)calculated_value;

    _RegAccessParser_impl<dynamic>* parser = (_RegAccessParser_impl<dynamic>*)context;
    string path_to_compare = parser->_full_path ? calculated_path : get_legacy_path(*instance);

    for (const auto& entry : parser->_params_map)
    {
        const string& key = entry.first;
        const ParamType param_type = entry.second.type;
        const uint32_t value = entry.second.value;
        if (is_in_path(key, path_to_compare))
        {
            if (param_type == ParamType::INDEX && !instance->is_index())
            {
                throw MlxRegException("Field: %s is not an INDEX.", calculated_path.c_str());
            }
            else if (param_type == ParamType::OP && !instance->is_op())
            {
                throw MlxRegException("Field: %s is not an OP.", calculated_path.c_str());
            }
            else if (param_type == ParamType::DATA && !parser->_ignore_ro && instance->is_ro())
            {
                throw MlxRegException("Field: %s is ReadOnly", calculated_path.c_str());
            }
            uint32_t field_size = (uint32_t)instance->get_size();
            if (field_size == 0 || field_size > 32)
            {
                throw MlxRegException(
                  "Field '%s' has invalid size: %d bits (must be 1-32)", calculated_path.c_str(), field_size);
            }
            if (field_size < 32 && value > ((1u << field_size) - 1))
            {
                throw MlxRegException("Value: 0x%x exceeds maximum value for field '%s' (%d bits, max: 0x%x)",
                                      value,
                                      calculated_path.c_str(),
                                      field_size,
                                      (1u << field_size) - 1);
            }
            parser->updateBuffer((uint32_t)calculated_offset, field_size, value);
            parser->_params_map.erase(key);
            break;
        }
    }
    return false; // continue traversal
}
/************************************
 * Function: parse_register_params
 ************************************/
template<bool dynamic>
void _RegAccessParser_impl<dynamic>::parse_register_params(const string& data, ParamType param_type)
{
    if (data.empty())
    {
        return;
    }

    std::vector<string> datTokens = strSplit(data, ',', false);
    // Update buffer with data values
    for (typename std::vector<std::string>::size_type i = 0; i != datTokens.size(); i++)
    {
        std::vector<std::string> dat = strSplit(datTokens[i], '=', true);
        string datName = dat[0];
        string datVal = dat[1];
        u_int32_t uintVal;
        strToUint32((char*)datVal.c_str(), uintVal);
        if (_params_map.find(datName) != _params_map.end())
        {
            throw MlxRegException("Field: %s appears twice.", datName.c_str());
        }
        _params_map[datName] = {param_type, uintVal};
    }
}

/************************************
 * Function: parseUnknown
 ************************************/
template<bool dynamic>
void _RegAccessParser_impl<dynamic>::parseUnknown()
{
    // Split input strings
    std::vector<string> idxTokens = strSplit(_indexes, ',', false);
    updateBufferUnknwon(idxTokens);
    if (_data != "")
    {
        std::vector<string> datTokens = strSplit(_data, ',', false);
        updateBufferUnknwon(datTokens);
    }
}

/************************************
 * Function: updateBuffer
 ************************************/
template<bool dynamic>
void _RegAccessParser_impl<dynamic>::updateBuffer(u_int32_t offset, u_int32_t size, u_int32_t val)
{
    _buffer[offset >> 5] = MERGE(_buffer[offset >> 5], val, (offset % 32), size);
}

/************************************
 * Function: updateBufferUnknown
 *
 * Raw Data/Indexes Pattern is:
 * Address.Offset:Size=Data
 * Where:
 * Address: In bytes (DWORD aligned).
 * Offset:  In bits within the address.
 * Size:    In bits.
 ************************************/
template<bool dynamic>
void _RegAccessParser_impl<dynamic>::updateBufferUnknwon(std::vector<string> fieldTokens)
{
    // Parse fields
    for (typename std::vector<std::string>::size_type i = 0; i != fieldTokens.size(); i++)
    {
        std::vector<std::string> fieldToken = strSplit(fieldTokens[i], '=', true);
        // extract <data>
        u_int32_t fieldDataUint;
        string fieldDataStr = fieldToken[1];
        strToUint32((char*)fieldDataStr.c_str(), fieldDataUint);
        // <Address>.<Offset>:<size>
        string fieldPropStr = fieldToken[0];
        std::vector<std::string> fieldPropVec = strSplit(fieldPropStr, ':', true);
        // extract <size>
        u_int32_t fieldSizeUint;
        string fieldSizeStr = fieldPropVec[1];
        strToUint32((char*)fieldSizeStr.c_str(), fieldSizeUint);
        // validate <size>
        if (fieldSizeUint > 32)
        {
            throw MlxRegException("Invalid size: %d. max size = 32 (bits)", fieldSizeUint);
        }
        // validate value fits within the field size
        if (fieldSizeUint < 32 && fieldDataUint > ((1u << fieldSizeUint) - 1))
        {
            throw MlxRegException("Value: 0x%x exceeds maximum value for a %d-bit field (max: 0x%x)",
                                  fieldDataUint,
                                  fieldSizeUint,
                                  (1u << fieldSizeUint) - 1);
        }        
        // extract <address> <offset>
        string fieldLocStr = fieldPropVec[0];
        std::vector<std::string> fieldLocVec = strSplit(fieldLocStr, '.', true);
        // extract <address>
        u_int32_t fieldAddrUint;
        string fieldAddrStr = fieldLocVec[0];
        strToUint32((char*)fieldAddrStr.c_str(), fieldAddrUint);
        // extract <offset>
        u_int32_t fieldOffsetUint;
        string fieldOffsetStr = fieldLocVec[1];
        strToUint32((char*)fieldOffsetStr.c_str(), fieldOffsetUint);
        // validation
        // Address out of range
        if (fieldAddrUint > (_buffer.size() << 2))
        {
            throw MlxRegException("Address: 0x%08x is out of range", fieldOffsetUint);
        }
        // Address not 4 bytes aligned
        if (fieldAddrUint % 0x4 != 0)
        {
            throw MlxRegException("Address: 0x%08x is not 4-bytes aligned", fieldOffsetUint);
        }
        // offset + size out of address range
        if ((fieldOffsetUint + fieldSizeUint) > 32)
        {
            throw MlxRegException("Offset: %d and Size: %d is out of address range", fieldOffsetUint, fieldSizeUint);
        }
        // Update buffer
        u_int32_t val = _buffer[fieldAddrUint >> 2];
        val = MERGE(val, fieldDataUint, fieldOffsetUint, fieldSizeUint);
        _buffer[fieldAddrUint >> 2] = val;
    }
}

/************************************
 * Function: strSplit
 ************************************/
template<bool dynamic>
std::vector<string> _RegAccessParser_impl<dynamic>::strSplit(string str, char delimiter, bool forcePairs)
{
    std::vector<string> internal;
    std::stringstream ss(str); // Turn the string into a stream.
    std::string tok;
    while (getline(ss, tok, delimiter))
    {
        internal.push_back((const string)tok);
    }
    // Vector must contain <key-values> otherwise it's invalid.
    if (forcePairs)
    {
        if (internal.size() != 2)
        {
            throw MlxRegException("Argument: %s is invalid", str.c_str());
        }
    }
    // Can't have an empty key/value
    for (typename std::vector<string>::size_type i = 0; i != internal.size(); i++)
    {
        if (internal[i].length() == 0)
        {
            throw MlxRegException("Argument: %s is invalid", str.c_str());
        }
    }
    return internal;
}

/************************************
 * Function: strToUint32
 ************************************/
template<bool dynamic>
void _RegAccessParser_impl<dynamic>::strToUint32(char* str, u_int32_t& uint)
{
    char* endp;
    errno = 0;
    uint = strtoul(str, &endp, 0);
    if (*endp || errno == ERANGE)
    {
        throw MlxRegException("Argument: %s is invalid.", str);
    }
    if (str[0] == '-')
    {
        throw MlxRegException("Argument: %s is invalid. It must be non-negative.", str);
    }
    return;
}

/************************************
 * Function: is_in_path
 * Description: Checks if partial path matches the end of full path.
 * For example, "c.d" matches "a.b.c.d" but not "a.b.xc.d"
 ************************************/
template<bool dynamic>
bool _RegAccessParser_impl<dynamic>::is_in_path(const string& partial, const string& full)
{
    return (full.length() >= partial.length() && full.substr(full.length() - partial.length()) == partial &&
            (full.length() == partial.length() || full[full.length() - partial.length() - 1] == '.'));
}

template<bool dynamic>
bool _RegAccessParser_impl<dynamic>::on_traverse_get_field(const string& calculated_path,
                                                           uint64_t calculated_offset,
                                                           uint64_t calculated_value,
                                                           AdbInstance* instance,
                                                           void* context)
{
    (void)calculated_value;

    FieldSearchContext* search_context = (FieldSearchContext*)context;
    const string& requested_name = search_context->name;
    uint32_t requested_size = search_context->size;
    uint32_t requested_offset = search_context->offset;
    bool offset_specified = search_context->offsetSpecified;

    if (is_in_path(requested_name, calculated_path) &&
        (requested_size == 0 || requested_size == instance->get_size() ||
         (offset_specified && requested_offset % 32 == calculated_offset % 32))) // TODO: remove the size and offset
                                                                                 // checks after fixing in mlxlink
    {
        search_context->instance = instance;
        return true; // found the field, stop the traversal
    }
    return false; // continue traversal
}

/************************************
 * Function: getField
 ************************************/
template<bool dynamic>
typename _RegAccessParser_impl<dynamic>::AdbInstance* _RegAccessParser_impl<dynamic>::getField(string name,
                                                                                               u_int32_t size,
                                                                                               u_int32_t offset,
                                                                                               bool offsetSpecified,
                                                                                               bool is_buffer_full)
{
    FieldSearchContext search_context = {name, size, offset, offsetSpecified, nullptr};
    uint32_t buffer_size = is_buffer_full ? _buffer.size() * sizeof(uint32_t) : _regNode->get_size() / 8;
    _adb->traverse_layout(_regNode,
                          "",
                          0,
                          (uint8_t*)&(_buffer[0]),
                          buffer_size,
                          _RegAccessParser_impl<dynamic>::on_traverse_get_field,
                          (void*)&search_context,
                          is_buffer_full,
                          false,
                          _full_path);
    if (search_context.instance)
    {
        return search_context.instance;
    }
    throw MlxRegException("Can't find field name: \"%s\"", name.c_str());
}

template<bool dynamic>
string _RegAccessParser_impl<dynamic>::getAccess(const AdbInstance* field)
{
    string access = field->getInstanceAttr("access");
    if (access.empty())
    {
        access = "N/A";
        if (field->parent)
        {
            access = getAccess(field->parent);
        }
    }
    return access;
}

template<bool dynamic>
void _RegAccessParser_impl<dynamic>::updateField(string field_name, u_int32_t value)
{
    AdbInstance* field = getField(field_name);
    updateBuffer(field->offset, field->get_size(), value);
}

template<bool dynamic>
u_int32_t _RegAccessParser_impl<dynamic>::getFieldValue(string field_name,
                                                        std::vector<u_int32_t>& buff,
                                                        u_int32_t size,
                                                        u_int32_t offset,
                                                        bool offsetSpecified,
                                                        bool is_buffer_full)
{
    AdbInstance* field = getField(field_name, size, offset, offsetSpecified, is_buffer_full);
    return (u_int32_t)field->popBuf((u_int8_t*)&buff[0]);
}

/************************************
 * Function: get_legacy_path
 ************************************/
template<bool dynamic>
string _RegAccessParser_impl<dynamic>::get_legacy_path(AdbInstance& instance)
{
    string path = instance.fullName(1);
    size_t last_dot = path.rfind('.');
    string parent_path = (last_dot != string::npos) ? path.substr(0, last_dot) : "";

    if (instance.parent->fieldDesc && instance.parent->fieldDesc->subNode == "uint64")
    {
        path = parent_path + "_" + instance.fieldDesc->name;
    }
    else
    {
        if (path[path.length() - 1] != ']')
        {
            path = parent_path + "." + instance.fieldDesc->name +
                   _AdbInstance_impl<true, uint32_t>::addPathSuffixForArraySupport(path);
        }
    }
    return path;
}

// Explicit template instantiations
namespace mlxreg
{
template class _RegAccessParser_impl<false>;
template class _RegAccessParser_impl<true>;
} // namespace mlxreg
