/*
 * Copyright (c) 2018, University of Kaiserslautern
 * 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.
 *
 * Authors:
 *    Johannes Feldmann
 */

#include "congenAddressDecoder.h"
#include "Utils.h"

#include <fstream>

using std::ifstream;
using std::cout;
using std::endl;

#include <set>

using std::set;

tinyxml2::XMLElement *CongenAddressDecoder::GetXMLNode(tinyxml2::XMLElement
                                                       *pRoot, std::string strName)
{
    tinyxml2::XMLElement *pNode = pRoot->FirstChildElement(strName.c_str());
    if (pNode == nullptr) {
        reportFatal("ConGenAddressDecorder",
                    "XML File corrupted. Missing node " + strName + ".");
    }
    return pNode;
}

unsigned int CongenAddressDecoder::GetUnsignedTextFromXMLNode(
    tinyxml2::XMLElement *pRoot)
{
    std::string str = pRoot->GetText();
    if (str.empty() || std::find_if(str.begin(), str.end(), [](char c) {
    return !std::isdigit(c);
    }) != str.end()) {
        reportFatal("ConGenAddressDecoder",
                    "Node " + std::string(pRoot->Name()) + " is empty or not a number.");
        return (unsigned)(-1);
    }
    return atol(str.c_str());
}

unsigned int CongenAddressDecoder::GetUnsignedAttrFromXMLNode(
    tinyxml2::XMLElement *pRoot, std::string strName)
{
    std::string str = pRoot->Attribute(strName.c_str());
    if (str.empty() || std::find_if(str.begin(), str.end(), [](char c) {
    return !std::isdigit(c);
    }) != str.end()) {
        reportFatal("ConGenAddressDecoder",
                    "Attribute " + strName + " is empty or not a number.");
        return (unsigned)(-1);
    }
    return atol(str.c_str());
}

CongenAddressDecoder::CongenAddressDecoder()
{

}

void CongenAddressDecoder::setConfiguration(std::string url)
{
    tinyxml2::XMLDocument doc;
    loadXML(url, doc);
    tinyxml2::XMLElement *pRoot = doc.RootElement();
    std::string xmlNodeName(pRoot->Name());

    if ( xmlNodeName != "CONGEN") {
        reportFatal("ConGenAddressDecorder",
                    "Root node name differs from \"CONGEN\". File format not supported.");
    }

    // Load basic configuration
    tinyxml2::XMLElement *pNode = GetXMLNode(pRoot, "NAME");
    m_strName = pNode->GetText();
    pNode = GetXMLNode(pRoot, "COSTS");
    m_nCost = GetUnsignedTextFromXMLNode(pNode);
    tinyxml2::XMLElement *pConfig = GetXMLNode(pRoot, "CONFIG");
    pNode = GetXMLNode(pConfig, "NUM_BANK_BITS");
    m_nBankBits = GetUnsignedTextFromXMLNode(pNode);
    pNode = GetXMLNode(pConfig, "NUM_ROW_BITS");
    m_nRowBits = GetUnsignedTextFromXMLNode(pNode);
    pNode = GetXMLNode(pConfig, "NUM_COLUMN_BITS");
    m_nColumnBits = GetUnsignedTextFromXMLNode(pNode);
    pNode = GetXMLNode(pConfig, "NUM_BL_BITS");
    m_nBurstLengthBits = GetUnsignedTextFromXMLNode(pNode);
    pNode = GetXMLNode(pConfig, "NUM_BYTE_BITS");
    m_nByteBits = GetUnsignedTextFromXMLNode(pNode);

    // Load address mapping
    unsigned id = 0;

    for (pNode = pRoot->FirstChildElement("SOLUTION"); pNode != nullptr;
            pNode = pNode->NextSiblingElement("SOLUTION")) {
        if (GetUnsignedAttrFromXMLNode(pNode, "ID") == id) {
            // Correct mapping was found.
            break;
        }
    }

    // If no mapping was found report error and return
    if (pNode == nullptr) {
        reportFatal("ConGenAddressDecoder",
                    "No mapping with ID " + std::to_string(id) + " was found.");
        return;
    }

    // get XOR connections
    // An XOR connection needs two parameters: A bank bit and a Row bit.
    for (tinyxml2::XMLElement *pXor = pNode->FirstChildElement("XOR");
            pXor != nullptr; pXor = pXor->NextSiblingElement("XOR")) {
        m_vXor.push_back(XOR(GetUnsignedAttrFromXMLNode(pXor, "BANK"),
                             GetUnsignedAttrFromXMLNode(pXor, "ROW")));
    }

    set<unsigned> sUsed;

    // get all bank bits
    // Each bank bit of the address will be stored with a counter value which assigns the bit position DecodedAddress struct.
    unsigned counter = 0;
    for (tinyxml2::XMLElement *pBank = pNode->FirstChildElement("BANK_BIT");
            pBank != nullptr; pBank = pBank->NextSiblingElement("BANK_BIT")) {
        unsigned nBank = GetUnsignedTextFromXMLNode(pBank);
        m_vBankBits.push_back(pair<unsigned, unsigned>(counter++, nBank));
        sUsed.insert(nBank);
    }

    // get all row bits bits
    // Each Row bit of the address will be stored with a counter value which assigns the bit position DecodedAddress struct.
    counter = 0;
    for (tinyxml2::XMLElement *pRow = pNode->FirstChildElement("ROW_BIT");
            pRow != nullptr; pRow = pRow->NextSiblingElement("ROW_BIT")) {
        unsigned nRow = GetUnsignedTextFromXMLNode(pRow);
        m_vRowBits.push_back(pair<unsigned, unsigned>(counter++, nRow));
        sUsed.insert(nRow);
    }

    // Add byte bits
    for (unsigned i = 0; i < m_nByteBits; i++) {
        sUsed.insert(i);
    }

    // Theset bits are ignored (fixed in Congen)
    sUsed.insert(30);
    sUsed.insert(31);

    // Create Column mapping
    // These bits are not stored in the JSON file, but can be generated. All bits, which are until now not used for any other purpose are column bits.
    // Each column bit of the address will be stored with a counter value which assigns the bit position DecodedAddress struct.
    counter = 0;
    for (unsigned i = 0; i < 32; i++) {
        if (sUsed.find(i) != sUsed.end())
            continue;   // Already mapped

        m_vColumnBits.push_back(pair<unsigned, unsigned>(counter++, i));
    }

    // Fill the amount map. This is copied from xmlAddressDecoder without further investigation
    amount["channel"] = 1;
    amount["bank"] = pow(2.0, m_vBankBits.size());
    amount["row"] = pow(2.0, m_vRowBits.size());
    amount["column"] = pow(2.0, m_vColumnBits.size());
    amount["bytes"] = pow(2.0, m_nByteBits);
}

DecodedAddress CongenAddressDecoder::decodeAddress(sc_dt::uint64 addr)
{
    DecodedAddress result;

    // Apply XOR
    // For each used xor:
    //   Get the bank bit and row bit. Apply a bitwise xor operator and save it back to the bank bit.
    for (auto it = m_vXor.begin(); it != m_vXor.end(); it++) {
        unsigned new_bank_bit;
        //                            Bank                        Row
        new_bank_bit = (((addr >> it->nBank) & 1) ^ ((addr >> it->nRow) & 1));
        addr &= ~(1 << it->nBank);
        addr |= new_bank_bit << it->nBank;
    }

    // Unsed
    result.bankgroup = 0;
    result.channel = 0;
    result.rank = 0;

    // Pass through of the three byte bits
    result.bytes = addr & 0x7;

    // Bank
    // it->second: position of the target bit in the address
    // it->first: target position of the bit in the variable
    // For each bank bit:
    //   shift address bit to position 0. Clear all other bits. shift it the right bank bit. Add it to the set of bank bits.
    result.bank = 0;
    for (auto it = m_vBankBits.begin(); it != m_vBankBits.end(); it++) {
        result.bank |= ((addr >> it->second) & 1) << it->first;
    }

    // Row
    // it->second: position of the target bit in the address
    // it->first: target position of the bit in the variable
    // For each row bit:
    //   shift address bit to position 0. Clear all other bits. shift it the right row bit. Add it to the set of row bits.
    result.row = 0;
    for (auto it = m_vRowBits.begin(); it != m_vRowBits.end(); it++) {
        result.row |= ((addr >> it->second) & 1) << it->first;
    }

    // Column
    // it->second: position of the target bit in the address
    // it->first: target position of the bit in the variable
    // For each column bit:
    //   shift address bit to position 0. Clear all other bits. shift it the right column bit. Add it to the set of column bits.
    result.column = 0;
    for (auto it = m_vColumnBits.begin(); it != m_vColumnBits.end(); it++) {
        result.column |= ((addr >> it->second) & 1) << it->first;
    }

    return result;
}

sc_dt::uint64 CongenAddressDecoder::encodeAddress(DecodedAddress n)
{
    sc_dt::uint64 address = 0;

    // Bank
    // it->first: position of the target bit in the DecodedAddress struct field
    // it->second: target position of the bit in the address
    // For each bank bit:
    //   shift bank bit to position 0. Clear all other bits. shift it the right address bit. Add it to the set of address bits.
    for (auto it = m_vBankBits.begin(); it != m_vBankBits.end(); it++) {
        address |= ((n.bank >> it->first) & 1) << it->second;
    }
    // Row
    // it->first: position of the target bit in the DecodedAddress struct field
    // it->second: target position of the bit in the address
    // For each row bit:
    //   shift row bit to position 0. Clear all other bits. shift it the right address bit. Add it to the set of address bits.
    for (auto it = m_vRowBits.begin(); it != m_vRowBits.end(); it++) {
        address |= ((n.row >> it->first) & 1) << it->second;
    }
    // Column
    // it->first: position of the target bit in the DecodedAddress struct field
    // it->second: target position of the bit in the address
    // For each column bit:
    //   shift column bit to position 0. Clear all other bits. shift it the right address bit. Add it to the set of address bits.
    for (auto it = m_vColumnBits.begin(); it != m_vColumnBits.end(); it++) {
        address |= ((n.column >> it->first) & 1) << it->second;
    }

    // Add the unchanged byte bits
    address |= n.bytes;

    // Apply XOR
    // For each used xor:
    //   Get the bank bit and row bit. Apply a bitwise xor operator and save it back to the bank bit.
    for (auto it = m_vXor.begin(); it != m_vXor.end(); it++) {
        unsigned new_bank_bit;
        new_bank_bit = (((address >> it->nBank) & 1) ^ ((address >> it->nRow) & 1));
        address &= ~(1 << it->nBank);
        address |= new_bank_bit << it->nBank;
    }

    return address;
}

bool CongenAddressDecoder::testConfigFile(std::string url)
{
    // Simple test if the root node has the correct name.
    // This is suitable for now, but can be extended in future.

    tinyxml2::XMLDocument doc;
    loadXML(url, doc);
    tinyxml2::XMLElement *pRoot = doc.RootElement();

    return (strcmp(pRoot->Name(), "CONGEN") == 0);
}

void CongenAddressDecoder::print()
{
    map<unsigned, pair<unsigned, char>> output;

    for (auto it = m_vBankBits.begin(); it != m_vBankBits.end(); it++) {
        output[it->second] = pair<unsigned, char>(it->first, 'B');
    }
    for (auto it = m_vRowBits.begin(); it != m_vRowBits.end(); it++) {
        output[it->second] = pair<unsigned, char>(it->first, 'R');
    }
    for (auto it = m_vColumnBits.begin(); it != m_vColumnBits.end(); it++) {
        output[it->second] = pair<unsigned, char>(it->first, 'C');
    }

    // add byte bits
    output[0] = pair<unsigned, char>(0, 'b');
    output[1] = pair<unsigned, char>(1, 'b');
    output[2] = pair<unsigned, char>(2, 'b');

    cout << "Used addressmapping:" << endl;
    cout << headline << endl;
    for (unsigned i = 0; i < 32; i++) {
        cout << "  " << i << "  ";
    }
    cout << endl;
    for (unsigned i = 0; i < 32; i++) {
        cout << " " << output[i].second << "(" << output[i].first << ") ";
    }
    cout << endl;
}
