/*
 * Copyright (C) Jan 2019 Mellanox Technologies Ltd. All rights reserved.
 * Copyright (c) 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.
 *
 */
/*************************** AdbInstance ***************************/
/**
 * Function: AdbInstance::AdbInstance
 **/

#include "adb_instance.h"
#include "adb_node.h"
#include "adb_field.h"
#include "adb_expr.h"
#include "adb_exceptionHolder.h"
#include "buf_ops.h"
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

string addPathSuffixForArraySupport(string path)
{
    if (path[path.length() - 1] == ']')
    { // Won't add suffix if leaf is in Array
        return "";
    }
    size_t pos = 0;
    string suffix = "";
    while ((pos = path.find("[")) != std::string::npos)
    { // Add array index number in path to suffix.
        size_t end_pos = path.find("]");
        size_t len = end_pos - pos - 1;
        suffix = suffix + "_" + path.substr(pos + 1, len);
        path.erase(0, end_pos + 1);
    }
    return suffix;
}

AdbInstance::AdbInstance() :
    fieldDesc(NULL),
    nodeDesc(NULL),
    parent(NULL),
    offset(0xffffffff),
    size(0),
    maxLeafSize(0),
    arrIdx(0),
    isNameBeenExtended(false),
    unionSelector(NULL),
    isDiff(false),
    userData(NULL)

{
}

/**
 * Function: AdbInstance::AdbInstance
 **/
AdbInstance::~AdbInstance()
{
    for (size_t i = 0; i < subItems.size(); i++)
        delete subItems[i];
}

/**
 * Function: AdbInstance::isLeaf
 **/
bool AdbInstance::isLeaf()
{
    return nodeDesc == NULL;
}

/**
 * Function: isUnionisUnion
 **/
bool AdbInstance::isUnion()
{
    return isNode() && nodeDesc->isUnion;
}

/**
 * Function: AdbInstance::isStruct
 **/
bool AdbInstance::isStruct()
{
    return isNode() && !isUnion();
}

/**
 * Function: AdbInstance::isNode
 **/
bool AdbInstance::isNode()
{
    return !isLeaf();
}

/**
 * Function: AdbInstance::isNode
 **/
bool AdbInstance::isPartOfArray()
{
    return fieldDesc->isArray();
}

/**
 * Function: AdbInstance::fullName
 **/
string AdbInstance::fullName(int skipLevel)
{
    list<string> fnList;
    AdbInstance* p = parent;

    fnList.push_front(name);
    while (p != NULL)
    {
        fnList.push_front(p->name);
        p = p->parent;
    }

    if ((int)fnList.size() > skipLevel)
    {
        while (skipLevel--)
        {
            fnList.pop_front();
        }

        return boost::algorithm::join(fnList, ".");
    }
    else
    {
        return fnList.back();
    }
}

/**
 * Function: AdbInstance::fullName
 **/
bool AdbInstance::isReserved()
{
    return fieldDesc->isReserved;
}

/**
 * Function: AdbInstance::fullName
 **/
u_int32_t AdbInstance::dwordAddr()
{
    return (offset >> 5) << 2;
}

/**
 * Function: AdbInstance::fullName
 **/
u_int32_t AdbInstance::startBit()
{
    return offset % 32;
}

/**
 * Function: AdbInstance::operator<
 **/
bool AdbInstance::operator<(const AdbInstance& other)
{
    return offset < other.offset;
}

/**
 * Function: AdbInstance::getChildByPath
 **/
AdbInstance* AdbInstance::getChildByPath(const string& path, bool isCaseSensitive)
{
    string effPath = isCaseSensitive ? path : boost::algorithm::to_lower_copy(path);
    if (effPath[0] == '.')
    {
        effPath.erase(0, 1);
    }

    size_t idx = effPath.find(".");
    string childName = idx == string::npos ? effPath : effPath.substr(0, idx);
    string grandChildPath = idx == string::npos ? string() : effPath.substr(idx + 1);

    if (path.empty())
    {
        return this;
    }

    // Search for childName
    AdbInstance* child = NULL;
    for (size_t i = 0; i < subItems.size(); i++)
    {
        string subName = isCaseSensitive ? subItems[i]->name : boost::algorithm::to_lower_copy(subItems[i]->name);
        if (subName == childName)
        {
            child = subItems[i];
            break;
        }
    }

    if (!child)
    {
        return NULL;
    }

    // if grandChildPath isn't empty this means we need to continue and find the desired field in grand child node
    return grandChildPath.empty() ? child : child->getChildByPath(grandChildPath, isCaseSensitive);
}

/**
 * Function: AdbInstance::findChild
 **/
vector<AdbInstance*> AdbInstance::findChild(const string& childName, bool isCaseSensitive, bool by_inst_name)
{
    string effName = isCaseSensitive ? childName : boost::algorithm::to_lower_copy(childName);
    vector<AdbInstance*> childList;

    if (by_inst_name || isLeaf())
    {
        if (name == childName)
        {
            childList.push_back(this);
        }
    }
    else
    {
        if (isNode() && nodeDesc->name == childName)
        {
            childList.push_back(this);
        }
    }

    // do that recursively for all child items
    for (size_t i = 0; i < subItems.size(); i++)
    {
        vector<AdbInstance*> l = subItems[i]->findChild(effName, true);
        childList.insert(childList.end(), l.begin(), l.end());
    }

    return childList;
}

/**
 * Function: AdbInstance::getAttr
 **/
string AdbInstance::getInstanceAttr(const string& attrName) const
{
    AttrsMap::const_iterator it = instAttrsMap.find(attrName);
    if (it == instAttrsMap.end())
    {
        if (fieldDesc)
        {
            it = fieldDesc->attrs.find(attrName);
            if (it == fieldDesc->attrs.end())
            {
                return string();
            }
        }
        else
        {
            return string();
        }
    }
    return it->second;
}

/**
 * Function: AdbInstance::getInstanceAttrIterator
 **/
AttrsMap::iterator AdbInstance::getInstanceAttrIterator(const string& attrName, bool& found)
{
    AttrsMap::iterator it = instAttrsMap.find(attrName);
    found = false;
    if (it != instAttrsMap.end())
    {
        found = true;
    }
    else if (fieldDesc)
    {
        it = fieldDesc->attrs.find(attrName);
        if (it != fieldDesc->attrs.end())
        {
            found = true;
        }
    }
    return it;
}

/**
 * Function: AdbInstance::setInstanceAttr
 **/
void AdbInstance::setInstanceAttr(const string& attrName, const string& attrValue)
{
    instAttrsMap[attrName] = attrValue;
}

/*
 * Function: AdbInstance::copyAllInstanceAttr - copies all attrs from attributes structure
 **/
void AdbInstance::copyAllInstanceAttr(AttrsMap& attsToCopy)
{
    instAttrsMap = attsToCopy;
}

/**
 * Function: AdbInstance::getFullInstanceAttrsMapCopy - returns the instance attr map by val
 **/
AttrsMap AdbInstance::getFullInstanceAttrsMapCopy()
{
    AttrsMap tmpCopy;
    // copy first the fields attr map
    for (AttrsMap::iterator it = fieldDesc->attrs.begin(); it != fieldDesc->attrs.end(); it++)
    {
        tmpCopy[it->first] = it->second;
    }
    // copy the overriden variables - in instAttrsMap
    for (AttrsMap::iterator it = instAttrsMap.begin(); it != instAttrsMap.end(); it++)
    {
        tmpCopy[it->first] = it->second;
    }
    return tmpCopy;
}

/**
 * Function: AdbInstance::setVarsMap - set an item in vars map
 **/
void AdbInstance::setVarsMap(const string& attrName, const string& attrValue)
{
    varsMap[attrName] = attrValue;
}

/**
 * Function: AdbInstance::setVarsMap - set an item in vars map
 **/
void AdbInstance::setVarsMap(const AttrsMap& AttrsMap)
{
    varsMap = AttrsMap;
}

/**
 * Function: AdbInstance::setVarsMap - returns a copy of vars map
 **/
AttrsMap AdbInstance::getVarsMap()
{
    return varsMap;
}

/**
 * Function: AdbInstance::isEnumExists
 **/
bool AdbInstance::isEnumExists()
{
    return !getInstanceAttr("enum").empty();
}

/**
 * Function: AdbInstance::enumToInt
 **/
bool AdbInstance::enumToInt(const string& name, u_int64_t& val)
{
    vector<string> enumValues;
    string enums = getInstanceAttr("enum");
    boost::algorithm::split(enumValues, enums, boost::is_any_of(string(",")));

    for (size_t i = 0; i < enumValues.size(); i++)
    {
        vector<string> pair;
        string trimedEnumValues = enumValues[i];
        boost::algorithm::trim(trimedEnumValues);
        boost::algorithm::split(pair, trimedEnumValues, boost::is_any_of(string("=")));

        if (pair.size() != 2)
        {
            continue;
        }

        char* end;
        if (pair[0] == name)
        {
            val = strtoul(pair[1].c_str(), &end, 0);
            if (*end != '\0')
            {
                return false;
            }
            return true;
        }
    }

    return false;
}

/**
 * Function: AdbInstance::intToEnum
 **/
bool AdbInstance::intToEnum(u_int64_t val, string& valName)
{
    vector<string> enumValues;
    string enums = getInstanceAttr("enum");
    boost::algorithm::split(enumValues, enums, boost::is_any_of(string(",")));

    for (size_t i = 0; i < enumValues.size(); i++)
    {
        vector<string> pair;
        string trimedEnumValues = enumValues[i];
        boost::algorithm::trim(trimedEnumValues);
        boost::algorithm::split(pair, trimedEnumValues, boost::is_any_of(string("=")));

        if (pair.size() != 2)
        {
            continue;
        }

        char* end;
        u_int64_t tmp = strtoul(pair[1].c_str(), &end, 0);
        if (*end != '\0')
        {
            continue;
        }

        if (tmp == val)
        {
            valName = pair[0];
            return true;
        }
    }

    return false;
}

/**
 * Function: AdbInstance::getEnumMap
 **/
map<string, u_int64_t> AdbInstance::getEnumMap()
{
    map<string, u_int64_t> enumMap;
    vector<string> enumValues;
    string enums = getInstanceAttr("enum");
    boost::algorithm::split(enumValues, enums, boost::is_any_of(string(",")));

    for (size_t i = 0; i < enumValues.size(); i++)
    {
        vector<string> pair;
        string trimedEnumValues = enumValues[i];
        boost::algorithm::trim(trimedEnumValues);
        boost::algorithm::split(pair, trimedEnumValues, boost::is_any_of(string("=")));
        if (pair.size() != 2)
        {
            throw AdbException("Can't parse enum: " + enumValues[i]);
            // continue;
        }

        char* end;
        u_int64_t intVal = strtoul(pair[1].c_str(), &end, 0);
        if (*end != '\0')
        {
            throw AdbException(string("Can't evaluate enum (") + pair[0].c_str() + "): " + pair[1].c_str());
            // continue;
        }

        enumMap[pair[0]] = intVal;
    }

    return enumMap;
}

/**
 * Function: AdbInstance::getEnumValues
 **/
vector<u_int64_t> AdbInstance::getEnumValues()
{
    vector<u_int64_t> values;
    vector<string> enumValues;
    string enums = getInstanceAttr("enum");
    boost::algorithm::split(enumValues, enums, boost::is_any_of(string(",")));

    for (size_t i = 0; i < enumValues.size(); i++)
    {
        vector<string> pair;
        string trimedEnumValues = enumValues[i];
        boost::algorithm::trim(trimedEnumValues);
        boost::algorithm::split(pair, trimedEnumValues, boost::is_any_of(string("=")));
        if (pair.size() != 2)
        {
            continue;
        }

        char* end;
        u_int64_t intVal = strtoul(pair[1].c_str(), &end, 0);
        if (*end != '\0')
        {
            continue;
        }

        values.push_back(intVal);
    }

    return values;
}

/**
 * Function: AdbInstance::getUnionSelectedNodeName
 **/
AdbInstance* AdbInstance::getUnionSelectedNodeName(const u_int64_t& selectorVal)
{
    if (!isUnion())
    {
        throw AdbException("This is not union node (%s), can't get selected node name", fullName().c_str());
    }

    if (!unionSelector)
    {
        throw AdbException("Can't find selector for union: " + name);
    }

    map<string, u_int64_t> selectorValMap = unionSelector->getEnumMap();
    for (map<string, u_int64_t>::iterator it = selectorValMap.begin(); it != selectorValMap.end(); it++)
    {
        if (it->second == selectorVal)
        {
            const string& selectorEnum = it->first;
            // search for the sub instance with the "selected_by" attribute == selectorEnum
            for (size_t i = 0; i < subItems.size(); i++)
            {
                if (subItems[i]->getInstanceAttr("selected_by") == selectorEnum)
                {
                    return subItems[i];
                }
            }
            throw AdbException("Found selector value (" + selectorEnum + ") is defined for selector field (" +
                               unionSelector->name + ") but no appropriate subfield of this union was found");
        }
    }

    throw AdbException("Union selector field (" + unionSelector->name + ") doesn't define selector value (" +
                       boost::lexical_cast<string>(selectorVal));
}

/**
 * Function: AdbInstance::getUnionSelectedNodeName
 **/
AdbInstance* AdbInstance::getUnionSelectedNodeName(const string& selectorEnum)
{
    if (!isUnion())
    {
        throw AdbException("This is not union node (%s), can't get selected node name", fullName().c_str());
    }

    if (!unionSelector)
    {
        throw AdbException("Can't find selector for union: " + name);
    }

    for (size_t i = 0; i < subItems.size(); i++)
    {
        if (subItems[i]->getInstanceAttr("selected_by") == selectorEnum)
        {
            return subItems[i];
        }
    }

    throw AdbException("Union selector field (" + unionSelector->name + ") doesn't define a selector value (" +
                       selectorEnum + ")");
}

/**
 * Function: AdbInstance::isConditionalNode
 * Throws exception: AdbException
 **/
bool AdbInstance::isConditionalNode()
{
    if (isNode())
    {
        return (getInstanceAttr("is_conditional") == "1");
    }
    return false;
}

/**
 * Function: AdbInstance::isConditionValid
 * Throws exception: AdbException
 **/
bool AdbInstance::isConditionValid(map<string, string>* valuesMap)
{
    u_int64_t res;
    AdbExpr expressionChecker;
    int status = -1;
    char* condExp;
    char* exp;

    if (fieldDesc->condition.empty())
    {
        return true;
    }

    condExp = new char[fieldDesc->condition.size() + 1];
    exp = condExp;
    if (!exp)
    {
        throw AdbException("Memory allocation error");
    }
    strcpy(exp, fieldDesc->condition.c_str());

    expressionChecker.setVars(valuesMap);
    try
    {
        status = expressionChecker.expr(&exp, &res);
    }
    catch (AdbException& e)
    {
        delete[] condExp;
        throw AdbException(string("AdbException: ") + e.what_s());
    }
    delete[] condExp;

    if (status < 0)
    {
        throw AdbException(string("Error evaluating expression \"") + fieldDesc->condition.c_str() +
                           "\" : " + AdbExpr::statusStr(status));
    }

    return (status >= 0 && res != 0);
}

/**
 * Function: AdbInstance::getLeafFields
 **/
vector<AdbInstance*> AdbInstance::getLeafFields(bool extendenName)
{
    vector<AdbInstance*> fields;

    for (size_t i = 0; i < subItems.size(); i++)
    {
        if (subItems[i]->isNode())
        {
            vector<AdbInstance*> subFields = subItems[i]->getLeafFields(extendenName);
            fields.insert(fields.end(), subFields.begin(), subFields.end());
        }
        else
        {
            if (extendenName && !subItems[i]->isNameBeenExtended)
            {
                if (subItems[i]->parent->fieldDesc->subNode == "uint64")
                {
                    subItems[i]->name = subItems[i]->parent->name + "_" + subItems[i]->name;
                }
                else
                {
                    subItems[i]->name = subItems[i]->name + addPathSuffixForArraySupport(subItems[i]->fullName());
                }
                subItems[i]->isNameBeenExtended = true;
            }
            fields.push_back(subItems[i]);
        }
    }

    return fields;
}

/**
 * Function: AdbInstance::pushBuf
 **/
void AdbInstance::pushBuf(u_int8_t* buf, u_int64_t value)
{
    push_to_buf(buf, offset, size, value);
}

/**
 * Function: AdbInstance::popBuf
 **/
u_int64_t AdbInstance::popBuf(u_int8_t* buf)
{
    return pop_from_buf(buf, offset, size);
}

/**
 * Function: AdbInstance::print
 **/
void AdbInstance::print(int indent)
{
    string indentStr = indentString(indent);
    printf("%sfullName: %s, offset: 0x%x.%d, size: 0x%x.%d, isNode:%d, isUnion:%d\n", indentStr.c_str(),
           fullName().c_str(), (offset >> 5) << 2, offset % 32, (size >> 5) << 2, size % 32, isNode(), isUnion());

    if (isNode())
    {
        for (size_t i = 0; i < subItems.size(); i++)
            subItems[i]->print(indent + 1);
    }
}
