#include "leak_detector.h"

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>
#include <algorithm>

#include "DvmhDirective.h"
#include "../Distribution/Array.h"
#include "../Distribution/Arrays.h"
#include "../Distribution/GraphCSR.h"
#include "errors.h"
#include "utils.h"
#include "json.hpp"

#include "graph_calls_func.h"

using std::vector;
using std::tuple;
using std::get;
using std::string;
using std::pair;
using std::set;
using std::map;

using std::set_union;
using std::make_pair;
using std::min;
using std::max;

using nlohmann::json;

template<typename setT>
static void uniteSets(const set<setT> &first, const set<setT> &second, set<setT> &result)
{
    vector<setT> unitedVector(first.size() + second.size());
    auto it = set_union(first.begin(), first.end(), second.begin(), second.end(), unitedVector.begin());
    unitedVector.resize(it - unitedVector.begin());
    for (int i = 0; i < (int)unitedVector.size(); ++i)
        result.insert(unitedVector[i]);
}

template<typename keyT, typename valT>
static void uniteSets(const map<keyT, valT> &first, const map<keyT, valT> &second, map<keyT, valT> &result)
{
    vector<pair<keyT, valT>> unitedVector(first.size() + second.size());
    auto it = set_union(first.begin(), first.end(), second.begin(), second.end(), unitedVector.begin());
    unitedVector.resize(it - unitedVector.begin());
    for (int i = 0; i < (int)unitedVector.size(); ++i)
        result.insert(unitedVector[i]);
}

template<typename keyT, typename valT>
static void uniteReduction(const map<keyT, valT> &first, const map<keyT, valT> &second, map<keyT, valT> &result)
{
    auto itF = first.begin();
    for (auto &redList : result)
    {
        auto itFound = second.find(redList.first);
        if (itFound != second.end())
            uniteSets(itF->second, itFound->second, redList.second);
        ++itF;
    }

    for (auto redList : second)
    {
        auto itFound = result.find(redList.first);
        if (itFound == result.end())
            result.insert(itFound, redList);
    }
}

template<typename vTuples>
static vTuples uniteOnRules(const vTuples &first, const vTuples &second)
{
    vTuples result(first.size());
    for (int i = 0; i < (int)first.size(); ++i)
    {
        bool firstStar = first[i].first == "*";
        bool secondStar = second[i].first == "*";
        if (firstStar && secondStar)
            result[i].first = "*";
        else if (firstStar)
            result[i] = second[i];
        else
            result[i] = first[i];
    }
    return result;
}

template<typename vTuples>
static bool hasConflictUniteOnRules(const vTuples& first, const vTuples& second)
{
    if (first.size() != second.size())
        return true;

    for (int i = 0; i < (int)first.size(); ++i)
    {
        bool firstStar = first[i].first == "*";
        bool secondStar = second[i].first == "*";
        if (!firstStar && !secondStar)
            return true;
    }
    return false;
}

ParallelDirective* operator+(const ParallelDirective &left, const ParallelDirective &right)
{
    const ParallelDirective *first = &left;
    const ParallelDirective *second = &right;

    ParallelDirective *result = NULL;
    checkNull(first, convertFileName(__FILE__).c_str(), __LINE__);
    checkNull(second, convertFileName(__FILE__).c_str(), __LINE__);

    bool condition = first->arrayRef == second->arrayRef;
    if (sharedMemoryParallelization)
        condition = !hasConflictUniteOnRules(first->on, second->on) && !hasConflictUniteOnRules(first->on2, second->on2);

    if (condition)
    {
        if (first->on.size() != second->on.size())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        result = new ParallelDirective();

        result->langType = first->langType;
        result->file = first->file;
        result->line = first->line;
        result->col = first->col;

        result->arrayRef = first->arrayRef;
        result->arrayRef2 = first->arrayRef2;

        result->parallel = vector<string>(first->parallel.size() + second->parallel.size());
        result->on = vector<pair<string, pair<int, int>>>(first->on.size());

        for (int i = 0; i < (int)first->parallel.size(); ++i)
            result->parallel[i] = first->parallel[i];

        for (int i = 0; i < (int)second->parallel.size(); ++i)
            result->parallel[i + first->parallel.size()] = second->parallel[i];

        result->on = uniteOnRules(first->on, second->on);
        result->on2 = uniteOnRules(first->on2, second->on2);

        uniteVectors(first->across, second->across, result->across);
        uniteVectors(first->shadowRenew, second->shadowRenew, result->shadowRenew);

        uniteSets(first->privates, second->privates, result->privates);
        uniteSets(first->remoteAccess, second->remoteAccess, result->remoteAccess);

        result->reduction = first->reduction;
        result->reductionLoc = first->reductionLoc;

        uniteReduction(first->reduction, second->reduction, result->reduction);
        uniteReduction(first->reductionLoc, second->reductionLoc, result->reductionLoc);
    }
    else
        result = new ParallelDirective(left);
    return result;
}

static inline int findRule(const int alingDim, const vector<tuple<DIST::Array*, int, pair<int, int>>> &rule, pair<int, pair<int, int>> &retVal)
{
    for (int i = 0; i < rule.size(); ++i)
    {
        if (get<1>(rule[i]) == alingDim)
        {
            retVal = make_pair(alingDim, get<2>(rule[i]));
            return 0;
        }
    }
    return -1;
}

static inline bool isNonDistributedDim(const vector<tuple<DIST::Array*, int, pair<int, int>>> &ruleForOn,
                                       const vector<tuple<DIST::Array*, int, pair<int, int>>> &ruleForShadow,
                                       const int dimN, 
                                       const vector<pair<DIST::Array*, const DistrVariant*>> &distribution,
                                       const vector<pair<string, pair<int, int>>> &parallelOnRule)
{
    if (ruleForShadow.size() <= dimN)
        return false;

    if (get<0>(ruleForShadow[dimN]) == NULL)
        return true;

    //check for distributed in declaration or in redistr. rules
    const tuple<DIST::Array*, int, pair<int, int>> &toCheck = ruleForShadow[dimN];
    const int dimInTepml = get<1>(toCheck);

    for (auto &templ : distribution)
    {
        if (templ.first == get<0>(toCheck))
        {            
            if (templ.second->distRule[dimInTepml] == NONE)
                return true;
        }
    }

    //check for distributed in loop
    if (dimInTepml >= 0 && dimInTepml < parallelOnRule.size())
        if (parallelOnRule[dimInTepml].first == "*")
            return true;

    return false;
}

vector<tuple<DIST::Array*, int, pair<int, int>>> 
    getAlignRuleWithTemplate(DIST::Array *array, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls,
                             DIST::GraphCSR<int, double, attrType> &reducedG, const DIST::Arrays<int> &allArrays,
                             const uint64_t regionId)
{
    vector<tuple<DIST::Array*, int, pair<int, int>>> retVal;

    set<DIST::Array*> realRefs;
    getRealArrayRefs(array, array, realRefs, arrayLinksByFuncCalls);

    vector<vector<tuple<DIST::Array*, int, pair<int, int>>>> allRuleForShadow(realRefs.size());
    int idx = 0;
    for (auto &array : realRefs)
        reducedG.GetAlignRuleWithTemplate(array, allArrays, allRuleForShadow[idx++], regionId);

    if (realRefs.size() == 1)
        retVal = allRuleForShadow[0];
    else
    {
        bool eq = isAllRulesEqual(allRuleForShadow);
        if (eq == false)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        else
            retVal = allRuleForShadow[0];
    }
    return retVal;
}

bool needCorner(const DIST::Array* currArray, const vector<map<pair<int, int>, int>>& shiftsByAccess, const vector<vector<pair<int, int>>>& refsInLoop)
{
    for (auto& access : refsInLoop)
    {
        int countOfShadows = 0;
        if (access.size() < currArray->GetDimSize())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (int i = 0; i < currArray->GetDimSize(); ++i)
        {
            const pair<int, int>& coef = access[i];
            auto it = shiftsByAccess[i].find(coef);
            if (it != shiftsByAccess[i].end())
                if (it->second != 0)
                    countOfShadows++;
        }
        if (countOfShadows > 1)
            return true;
    }
    return false;
}

static inline string calculateShifts(DIST::GraphCSR<int, double, attrType> &reducedG,
                                     const DIST::Arrays<int> &allArrays,
                                     DIST::Array *arrayRef, DIST::Array *calcForArray,
                                     pair<pair<string, string>, vector<pair<int, int>>> &coeffs,
                                     vector<pair<int, int>> &shifts,
                                     vector<map<pair<int, int>, int>> &shiftsByAccess,
                                     map<DIST::Array*, vector<ArrayOp>>& remoteRegularReads,
                                     const vector<pair<string, pair<int, int>>> baseOnRule,
                                     const vector<pair<string, pair<int, int>>> parallelOnRule,
                                     const map<DIST::Array*, pair<vector<ArrayOp>, vector<bool>>> &readOps, const bool isAcross,
                                     const vector<pair<DIST::Array*, const DistrVariant*>> &distribution,
                                     const uint64_t regionId,
                                     const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    vector<tuple<DIST::Array*, int, pair<int, int>>> ruleForOn, ruleForShadow;

    if (!sharedMemoryParallelization)
    {
        ruleForOn = getAlignRuleWithTemplate(arrayRef, arrayLinksByFuncCalls, reducedG, allArrays, regionId);
        ruleForShadow = getAlignRuleWithTemplate(calcForArray, arrayLinksByFuncCalls, reducedG, allArrays, regionId);

    }
    string out = "";
    // check for distributed and not mapped dims -> zero them out ('coeffs.second')
    set<DIST::Array*> refs;
    getRealArrayRefs(calcForArray, calcForArray, refs, arrayLinksByFuncCalls);

    //TODO: need to correct errors
    /*if (!sharedMemoryParallelization)
    {
        for (auto& array : refs)
        {
            DIST::Array* tmpl = array->GetTemplateArray(regionId);
            checkNull(tmpl, convertFileName(__FILE__).c_str(), __LINE__);

            auto align = array->GetLinksWithTemplate(regionId);

            bool found = false;
            for (auto& t : distribution)
            {
                if (t.first == tmpl)
                {
                    found = true;

                    for (int aDim = 0; aDim < align.size(); ++aDim)
                    {
                        int link = align[aDim];
                        if (link != -1)
                        {
                            int tLink = link;
                            if (!arrayRef->IsTemplate())
                            {
                                auto alignMain = arrayRef->GetLinksWithTemplate(regionId);
                                for (int z = 0; z < alignMain.size(); ++z)
                                    if (alignMain[z] == tLink)
                                        tLink = z;
                            }

                            if (t.second->distRule[link] == dist::BLOCK && baseOnRule[aDim].first == "*")
                            {
                                for (int z = 0; z < coeffs.second.size(); ++z)
                                    coeffs.second[z].first = coeffs.second[z].second = 0;
                                return out;
                            }
                        }
                    }
                }
            }

            if (!found)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
    }*/

    const pair<vector<ArrayOp>, vector<bool>> *currReadOp = NULL;
    auto readIt = readOps.find(calcForArray);
    if (readIt != readOps.end())
        currReadOp = &(readIt->second);
        
    if(!sharedMemoryParallelization)
    {
        findAndReplaceDimentions(ruleForOn, allArrays);
        findAndReplaceDimentions(ruleForShadow, allArrays);
    }

    const int len = (int)coeffs.second.size();
    vector<pair<int, int>> shift(len);
        
    bool allZero = true;
    for (int k = 0; k < len; ++k)
    {
        shiftsByAccess.push_back(map<pair<int, int>, int>());
        shift[k].first = shift[k].second = 0;

        if (k != 0)
            out += ",";
        char buf[256];

        // calculate correct shifts from readOp info
        if (currReadOp)
        {
            // no unrecognized read operations
            if (currReadOp->second[k] == false)
            {
                if (sharedMemoryParallelization)
                {
                    for (auto& coeffs : currReadOp->first[k].coefficients)
                    {
                        auto currAccess = coeffs.first;

                        const int currShift = coeffs.first.second;

                        auto itFound = shiftsByAccess[k].find(currAccess);
                        if (itFound == shiftsByAccess[k].end())
                            itFound = shiftsByAccess[k].insert(itFound, make_pair(currAccess, currShift));
                    }
                }
                else if (get<0>(ruleForShadow[k]) != NULL)
                {
                    const pair<int, int> currRuleShadow = get<2>(ruleForShadow[k]);

                    pair<int, pair<int, int>> currRuleOn;
                    int err = findRule(get<1>(ruleForShadow[k]), ruleForOn, currRuleOn);
                    if (err == 0)
                    {
                        if (currRuleOn.first >= parallelOnRule.size())
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                        const pair<int, int> loopRule = DIST::Fx(parallelOnRule[currRuleOn.first].second, currRuleOn.second);

                        if (loopRule.first != 0)
                        {
                            int minShift = 9999999;
                            int maxShift = -9999999;

                            for (auto &coeffs : currReadOp->first[k].coefficients)
                            {
                                auto currAccess = coeffs.first;
                                auto result = DIST::Fx(currAccess, currRuleShadow);

                                if (result.first == loopRule.first)
                                {
                                    const int absShift = abs(result.second - loopRule.second);
                                    const int signShift = (result.second - loopRule.second) > 0 ? 1 : -1;

                                    const int currShift = signShift * (absShift / currRuleShadow.first + ((absShift % currRuleShadow.first) != 0));

                                    minShift = std::min(minShift, currShift);
                                    maxShift = std::max(maxShift, currShift);

                                    auto itFound = shiftsByAccess[k].find(currAccess);
                                    if (itFound == shiftsByAccess[k].end())
                                        itFound = shiftsByAccess[k].insert(itFound, make_pair(currAccess, currShift));
                                }
                                else // remote_access
                                {
                                    auto it = remoteRegularReads.find(calcForArray);
                                    if (it == remoteRegularReads.end())
                                        it = remoteRegularReads.insert(it, make_pair(calcForArray, vector<ArrayOp>(calcForArray->GetDimSize())));
                                    it->second[k].coefficients.insert(coeffs);
                                }
                            }

                            // inconsistent -> may be remote will add later...
                            // or SINGLE position
                            if (minShift == 9999999 && maxShift == -9999999)
                                minShift = maxShift = 0;

                            if (minShift == maxShift)
                            {
                                if (minShift == 0)
                                {
                                    if (parallelOnRule[currRuleOn.first].first != "SINGLE")
                                    {
                                        shift[k].first = -coeffs.second[k].first;
                                        shift[k].second = -coeffs.second[k].second;
                                    }
                                }
                                else
                                {
                                    shift[k].first = -minShift;
                                    shift[k].second = minShift;

                                    if (shift[k].first > 0 && shift[k].second < 0)
                                        shift[k].second = 0;
                                    else if (shift[k].first < 0 && shift[k].second > 0)
                                        shift[k].first = 0;

                                    shift[k].first -= coeffs.second[k].first;
                                    shift[k].second -= coeffs.second[k].second;
                                }
                            }
                            else if (currReadOp->first[k].coefficients.size() > 0)
                            {
                                if (minShift > 0 && maxShift > 0)
                                {
                                    shift[k].first = 0;
                                    shift[k].second = std::abs(maxShift) - coeffs.second[k].second;
                                }
                                else if (minShift < 0 && maxShift < 0)
                                {
                                    shift[k].first = std::abs(minShift) - coeffs.second[k].first;
                                    shift[k].second = 0;
                                }
                                else
                                {
                                    shift[k].first = std::abs(minShift) - coeffs.second[k].first;
                                    shift[k].second = std::abs(maxShift) - coeffs.second[k].second;
                                }
                            }
                        }
                    }
                }
            }
        }

        if(!sharedMemoryParallelization)
        {
            if (coeffs.second[k].first + shift[k].first < 0)
                shift[k].first = -coeffs.second[k].first;

            if (coeffs.second[k].second + shift[k].second < 0)
                shift[k].second = -coeffs.second[k].second;

            if (isAcross)
            {
                if (coeffs.second[k] == make_pair(0, 0))
                    shift[k] = make_pair(0, 0);
            }
            else if (isNonDistributedDim(ruleForOn, ruleForShadow, k, distribution, parallelOnRule))
            {
                shift[k].first = -coeffs.second[k].first;
                shift[k].second = -coeffs.second[k].second;
                shiftsByAccess[k].clear();
            }
        }

        sprintf(buf, "%d:%d", coeffs.second[k].first + shift[k].first, coeffs.second[k].second + shift[k].second);        
        shifts[k] = shift[k];

        if (coeffs.second[k].first + shift[k].first != 0 || coeffs.second[k].second + shift[k].second != 0)
            allZero = false;
        out += buf;
    }

    if (allZero)
        return "";
    else
        return out;
}

string ParallelDirective::genBounds(pair<pair<string, string>, vector<pair<int, int>>> &shadowOp,
                                    vector<pair<int, int>> &shadowOpShift,
                                    DIST::GraphCSR<int, double, attrType> &reducedG,
                                    DIST::Arrays<int> &allArrays, DIST::Array* shadowArray,
                                    map<DIST::Array*, vector<ArrayOp>>& remoteRegularReads,
                                    const map<DIST::Array*, pair<vector<ArrayOp>, vector<bool>>> &readOps,
                                    const bool isAcross,
                                    const uint64_t regionId,
                                    const vector<pair<DIST::Array*, const DistrVariant*>> &distribution,
                                    set<DIST::Array*> &arraysInAcross,
                                    vector<map<pair<int, int>, int>> &shiftsByAccess,
                                    const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls) const
{
        checkNull(shadowArray, convertFileName(__FILE__).c_str(), __LINE__);
    
    auto on_ext = on;

    if(!sharedMemoryParallelization)
    {
        //replace to template align ::on
        if (arrayRef->IsTemplate() == false)
        {
            vector<tuple<DIST::Array*, int, pair<int, int>>> ruleForRef =
                getAlignRuleWithTemplate(arrayRef, arrayLinksByFuncCalls, reducedG, allArrays, regionId);
            findAndReplaceDimentions(ruleForRef, allArrays);

            on_ext.clear();
            for (int i = 0; i < ruleForRef.size(); ++i)
            {
                if (get<0>(ruleForRef[i]))
                {
                    on_ext.resize(get<0>(ruleForRef[i])->GetDimSize());
                    break;
                }
            }
            if (on_ext.size() == 0)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            std::fill(on_ext.begin(), on_ext.end(), make_pair("*", make_pair(0, 0)));

            for (int i = 0; i < ruleForRef.size(); ++i)
                if (get<0>(ruleForRef[i]))
                    on_ext[get<1>(ruleForRef[i])] = on[i];        
        }

        //replace single dim to key word 'SINGLE'
        for (int i = 0; i < on_ext.size(); ++i)
    {
        if (on_ext[i].first != "*")
        {
            if (std::find(parallel.begin(), parallel.end(), on_ext[i].first) == parallel.end())
                on_ext[i].first = "SINGLE";
        }
    }
    }

    string ret = "";    
    if (isAcross)
    {
        arraysInAcross.insert(shadowArray);
        ret = calculateShifts(reducedG, allArrays, arrayRef, shadowArray, shadowOp, shadowOpShift, shiftsByAccess, remoteRegularReads, on, on_ext, readOps, isAcross, distribution, regionId, arrayLinksByFuncCalls);
    }
    else
    {
        if (arraysInAcross.find(shadowArray) == arraysInAcross.end())
            ret = calculateShifts(reducedG, allArrays, arrayRef, shadowArray, shadowOp, shadowOpShift, shiftsByAccess, remoteRegularReads, on, on_ext, readOps, isAcross, distribution, regionId, arrayLinksByFuncCalls);
    }

    return ret;
}

static void genVariants(const int dimNum, vector<dist> &currDist, std::vector<DistrVariant> &currdist)
{
    if (dimNum == 1)
    {
        DistrVariant var1(currDist);
        var1.distRule.push_back(dist::NONE);

        DistrVariant var2(currDist);
        var2.distRule.push_back(dist::BLOCK);

        currdist.push_back(var1);
        currdist.push_back(var2);
    }
    else
    {
        vector<dist> next(currDist);
        next.push_back(dist::NONE);
        genVariants(dimNum - 1, next, currdist);
        next.back() = dist::BLOCK;
        genVariants(dimNum - 1, next, currdist);
    }
}

void DataDirective::createDirstributionVariants(const vector<DIST::Array*> &arraysToDist)
{
    for (int i = 0; i < arraysToDist.size(); ++i)
    {
        vector<DistrVariant> currdist;
        vector<dist> currDist;
        genVariants(arraysToDist[i]->GetDimSize(), currDist, currdist);

        //deprecate by dims
        for (auto &variant : currdist)
        {
            for (int z = 0; z < arraysToDist[i]->GetDimSize(); ++z)
                if (arraysToDist[i]->IsDimDepracated(z) || !arraysToDist[i]->IsDimMapped(z))
                    variant.distRule[z] = dist::NONE;
        }
        distrRules.push_back(make_pair(arraysToDist[i], currdist));
    }
}

string DistrVariantBase::GenRuleBase(const vector<int> &newOrder) const
{
    string retVal = "";

    retVal += "(";
    for (int i = 0; i < distRule.size(); ++i)
    {
        if (newOrder.size() == 0)
        {
            if (distRule[i] == dist::NONE)
                retVal += "*";
            else if (distRule[i] == dist::BLOCK)
                retVal += "BLOCK";
        }
        else
        {
            if (distRule[newOrder[i]] == dist::NONE)
                retVal += "*";
            else if (distRule[newOrder[i]] == dist::BLOCK)
                retVal += "BLOCK";
        }

        if (i != distRule.size() - 1)
            retVal += ",";
    }
    retVal += ")";
    return retVal;
}

vector<string> DataDirective::GenRule(const vector<int> &rules) const
{
    vector<string> retVal;
    if (distrRules.size() < rules.size())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (int i = 0; i < rules.size(); ++i)
    {
        if (rules[i] < distrRules[i].second.size())
        {
            string tmp = distrRules[i].first->GetShortName();
            tmp += distrRules[i].second[rules[i]].GenRuleBase(distrRules[i].first->GetNewTemplateDimsOrder());
            retVal.push_back(tmp);
        }
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }

    return retVal;
}

vector<vector<dist>> DataDirective::GenRule(const vector<int> &rules, int) const
{
    vector<vector<dist>> retVal;
    if (distrRules.size() < rules.size())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (int i = 0; i < rules.size(); ++i)
    {
        if (rules[i] < distrRules[i].second.size())
            retVal.push_back(distrRules[i].second[rules[i]].distRule);
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }

    return retVal;
}

vector<string> DataDirective::GenAlignsRules() const
{
    vector<string> retVal;
    for (int i = 0; i < alignRules.size(); ++i)
        retVal.push_back(alignRules[i].GenRuleBase());
    return retVal;
}

string AlignRuleBase::GenRuleBase() const
{   
    string retVal = "";
    retVal += "ALIGN " + alignArray->GetShortName() + "(";
    for (int i = 0; i < alignRule.size(); ++i)
    {
        retVal += genStringExpr(alignNames[i], alignRule[i]);
        if (i != alignRule.size() - 1)
            retVal += ",";
    }
        
    retVal += ") WITH " + alignWith->GetShortName() + "(";
    vector<string> alignEachDim(alignWith->GetDimSize());
    for (int i = 0; i < alignWith->GetDimSize(); ++i)
        alignEachDim[i] = "*";

    for (int i = 0; i < alignRuleWith.size(); ++i)
        if (alignRuleWith[i].first != -1)
            alignEachDim[alignRuleWith[i].first] = genStringExpr(alignNames[i], alignRuleWith[i].second);

    auto newOrder = alignWith->GetNewTemplateDimsOrder();
    if (newOrder.size() != 0)
    {
        vector<string> alignEachDimNew(alignEachDim);
        for (int z = 0; z < newOrder.size(); ++z)
            alignEachDim[z] = alignEachDimNew[newOrder[z]];
    }

    for (int i = 0; i < alignWith->GetDimSize(); ++i)
    {
        retVal += alignEachDim[i];
        if (i != alignWith->GetDimSize() - 1)
            retVal += ",";
    }
    retVal += ")";

    return retVal;
}

json AlignRuleBase::toJson()
{
    json retVal;

    retVal["packedAlignArrayAddress"] = alignArray ? std::to_string((long long)alignArray) : std::to_string((long long)-1);
    retVal["packedAlignWithAddress"] = alignWith ? std::to_string((long long)alignWith) : std::to_string((long long)-1);

    json alignRules = json::array();
    for (auto& rule : alignRule)
    {
        json pair;
        pair["key"] = rule.first;
        pair["value"] = rule.second;
        alignRules.push_back(pair);
    }
    retVal["alignRule"] = alignRules;

    json alignRuleWithJ = json::array();
    for (auto& [dimNum, AB] : alignRuleWith)
    {
        json tuple;
        tuple["dimNum"] = dimNum;
        tuple["a"] = AB.first;
        tuple["b"] = AB.second;

        alignRuleWithJ.push_back(tuple);
    }
    retVal["alignRuleWith"] = alignRuleWithJ;
    return retVal;
}

pair<string, string> convertDigitToPositive(const int digit)
{
    char buf[16];
    string sign = " + ";
    if (digit < 0)
    {
        sign = " - ";
        int val = -digit;
        sprintf(buf, "%d", val);
    }
    else
        sprintf(buf, "%d", digit);

    return make_pair(sign, buf);
}

string genStringExpr(const string &letter, const pair<int, int> expr)
{
    string retVal = "";
    if (expr.first == 0 && expr.second == 0)
        retVal = "*";
    else if (expr.second == 0)
    {
        if (expr.first == 1)
            retVal = letter;
        else
        {
            pair<string, string> digit2 = convertDigitToPositive(expr.first);
            if (digit2.first == " - ")
                digit2.second = "(-" + digit2.second + ")";
            retVal = digit2.second + string(" * ") + letter;
        }
    }
    else
    {
        pair<string, string> digit1 = convertDigitToPositive(expr.second);
        if (expr.first == 1)
            retVal = letter + digit1.first + digit1.second;
        else
        {
            pair<string, string> digit2 = convertDigitToPositive(expr.first);
            if (digit2.first == " - ")
                digit2.second = "(-" + digit2.second + ")";
            retVal = digit2.second + string(" * ") + letter + digit1.first + digit1.second;
        }
    }
    return retVal;
}

const std::vector<std::string> AlignRuleBase::alignNames = { "iEX1", "iEX2", "iEX3", "iEX4", "iEX5", "iEX6", "iEX7" };