/**
 * Determines the iteration regions not present in the provided iteration 
 * regions.
 * 
 * @param iterRegions a vector filled with ordered and non-overlapping 
 *                    iteration regions each defined with pairs of integers 
 *                    (start1, end1, start2, end2, ...)
 * @return the iterations not present in iterRegions
 */
inline std::vector<size_t> invertIterationRanges(const std::vector<size_t>& iterRegions) {
    std::vector<size_t> inverted;
    if (iterRegions.empty()) {
        inverted.resize(2);
        inverted[1] = std::numeric_limits<size_t>::max();
        return inverted;
    }

    CPPADCG_ASSERT_UNKNOWN(iterRegions.size() % 2 == 0);
    inverted.reserve(iterRegions.size() + 4);

    if (iterRegions[0] != 0) {
        inverted.push_back(0);
        inverted.push_back(iterRegions[0] - 1);
    }

    for (size_t i = 2; i < iterRegions.size(); i += 2) {
        CPPADCG_ASSERT_UNKNOWN(iterRegions[i - 1] < iterRegions[i]);
        inverted.push_back(iterRegions[i - 1] + 1);
        inverted.push_back(iterRegions[i] - 1);
    }

    if (iterRegions.back() != std::numeric_limits<size_t>::max()) {
        inverted.push_back(iterRegions.back() + 1);
        inverted.push_back(std::numeric_limits<size_t>::max());
    }

    return inverted;
}
inline void LanguageC<Base>::printRandomIndexPatternDeclaration(std::ostringstream& os,
                                                                const std::string& indentation,
                                                                const std::set<RandomIndexPattern*>& randomPatterns) {
    for (RandomIndexPattern* ip : randomPatterns) {
        if (ip->getType() == IndexPatternType::Random1D) {
            /**
             * 1D
             */
            Random1DIndexPattern* ip1 = static_cast<Random1DIndexPattern*> (ip);
            const std::map<size_t, size_t>& x2y = ip1->getValues();

            std::vector<size_t> y(x2y.rbegin()->first + 1);
            for (const std::pair<size_t, size_t>& p : x2y)
                y[p.first] = p.second;

            os << indentation;
            printStaticIndexArray(os, ip->getName(), y);
        } else {
            CPPADCG_ASSERT_UNKNOWN(ip->getType() == IndexPatternType::Random2D);
            /**
             * 2D
             */
            Random2DIndexPattern* ip2 = static_cast<Random2DIndexPattern*> (ip);
            os << indentation;
            printStaticIndexMatrix(os, ip->getName(), ip2->getValues());
        }
    }
}
/**
 * Combines ordered and non-overlapping regions of iteration indexes.
 * 
 * @param iterRegions the existing iteration regions
 * @param newIterRegions the iteration regions to be added
 */
inline void combineNonOverlapingIterationRanges(std::vector<size_t>& iterRegions,
                                                const std::vector<size_t>& newIterRegions) {
    if (iterRegions.empty()) {
        iterRegions = newIterRegions;
        return;
    } else if (newIterRegions.empty()) {
        return;
    }

    /**
     * regions are assumed to be ordered and non-overlapping
     */
    std::vector<size_t>::iterator itPos = iterRegions.begin();
    std::vector<size_t>::const_iterator itNew;
    for (itNew = newIterRegions.begin(); itNew != newIterRegions.end(); ++itNew) {
        size_t pos = *itNew;
        itPos = std::lower_bound(itPos, iterRegions.end(), pos);
        if (itPos == iterRegions.end()) {
            iterRegions.insert(iterRegions.end(), itNew, newIterRegions.end());
            break; // done
        } else if (*itPos == pos) {
            // same value -> must merge
            itPos = iterRegions.erase(itPos);
        } else {
            itPos = iterRegions.insert(itPos, pos);
        }
    }
    CPPADCG_ASSERT_UNKNOWN(iterRegions.size() % 2 == 0);
}
inline CG<Base>& CG<Base>::operator-=(const CG<Base> &right) {
    if (isParameter() && right.isParameter()) {
        *value_ -= *right.value_;

    } else {
        CodeHandler<Base>* handler;
        if (isParameter()) {
            handler = right.getCodeHandler();

        } else if (right.isParameter()) {
            if (right.isIdenticalZero()) {
                return *this; // nothing to do
            }

            handler = getCodeHandler();

        } else {
            CPPADCG_ASSERT_UNKNOWN(getCodeHandler() == right.getCodeHandler());
            handler = getCodeHandler();
        }

        std::unique_ptr<Base> value;
        if (isValueDefined() && right.isValueDefined()) {
            value.reset(new Base(getValue() - right.getValue()));
        }

        makeVariable(*handler, new OperationNode<Base>(CGOpCode::Sub,{argument(), right.argument()}), value);
    }

    return *this;
}
    inline void addFreeArraySpace(const OperationNode<Base>& released) {
        size_t arrayStart = _varId[released] - 1;
        const size_t arraySize = released.getArguments().size();
        if (arraySize == 0)
            return; // nothing to do (no free space)
        size_t arrayEnd = arrayStart + arraySize - 1;

        std::map<size_t, size_t>::iterator it;
        if (arrayStart > 0) {
            // try to merge with previous free space
            it = _freeArrayEndSpace.find(arrayStart - 1); // previous
            if (it != _freeArrayEndSpace.end()) {
                arrayStart = it->second; // merge space
                _freeArrayEndSpace.erase(it);
                _freeArrayStartSpace.erase(arrayStart);
            }
        }

        // try to merge with the next free space
        it = _freeArrayStartSpace.find(arrayEnd + 1); // next
        if (it != _freeArrayStartSpace.end()) {
            arrayEnd = it->second; // merge space 
            _freeArrayStartSpace.erase(it);
            _freeArrayEndSpace.erase(arrayEnd);
        }

        _freeArrayStartSpace[arrayStart] = arrayEnd;
        _freeArrayEndSpace[arrayEnd] = arrayStart;

        CPPADCG_ASSERT_UNKNOWN(_freeArrayStartSpace.size() == _freeArrayEndSpace.size());
    }
    /**
     * Creates a new model
     *
     * @param name The model name
     */
    LinuxDynamicLibModel(LinuxDynamicLib<Base>* dynLib, const std::string& name) :
        FunctorGenericModel<Base>(name),
        _dynLib(dynLib) {

        CPPADCG_ASSERT_UNKNOWN(_dynLib != nullptr);

        this->init();
    }
inline CG<Base>& CG<Base>::operator*=(const CG<Base> &right) {
    if (isParameter() && right.isParameter()) {
        *value_ *= *right.value_;

    } else {
        CodeHandler<Base>* handler;
        if (isParameter()) {
            if (isIdenticalZero()) {
                return *this; // nothing to do (does not consider that right might be infinity)
            } else if (isIdenticalOne()) {
                *this = right;
                return *this;
            }

            handler = right.getCodeHandler();

        } else if (right.isParameter()) {
            if (right.isIdenticalZero()) {
                makeParameter(Base(0.0)); // does not consider that left might be infinity
                return *this;
            } else if (right.isIdenticalOne()) {
                return *this; // nothing to do
            }

            handler = getCodeHandler();

        } else {
            CPPADCG_ASSERT_UNKNOWN(getCodeHandler() == right.getCodeHandler());
            handler = getCodeHandler();
        }

        std::unique_ptr<Base> value;
        if (isValueDefined() && right.isValueDefined()) {
            value.reset(new Base(getValue() * right.getValue()));
        }

        makeVariable(*handler, new OperationNode<Base>(CGOpCode::Mul,{argument(), right.argument()}), value);
    }

    return *this;
}
inline std::string LanguageC<Base>::indexPattern2String(const IndexPattern& ip,
                                                        const std::vector<const IndexDclrOperationNode<Base>*>& indexes) {
    std::stringstream ss;
    switch (ip.getType()) {
        case IndexPatternType::Linear: // y = x * a + b
        {
            CPPADCG_ASSERT_KNOWN(indexes.size() == 1, "Invalid number of indexes");
            const LinearIndexPattern& lip = static_cast<const LinearIndexPattern&> (ip);
            return linearIndexPattern2String(lip, *indexes[0]);
        }
        case IndexPatternType::Sectioned:
        {
            CPPADCG_ASSERT_KNOWN(indexes.size() == 1, "Invalid number of indexes");
            const SectionedIndexPattern* lip = static_cast<const SectionedIndexPattern*> (&ip);
            const std::map<size_t, IndexPattern*>& sections = lip->getLinearSections();
            size_t sSize = sections.size();
            CPPADCG_ASSERT_UNKNOWN(sSize > 1);

            std::map<size_t, IndexPattern*>::const_iterator its = sections.begin();
            for (size_t s = 0; s < sSize - 1; s++) {
                const IndexPattern* lp = its->second;
                ++its;
                size_t xStart = its->first;

                ss << "(" << (*indexes[0]->getName()) << "<" << xStart << ")? "
                        << indexPattern2String(*lp, *indexes[0]) << ": ";
            }
            ss << indexPattern2String(*its->second, *indexes[0]);

            return ss.str();
        }

        case IndexPatternType::Plane2D: // y = f(x) + f(z)
        {
            CPPADCG_ASSERT_KNOWN(indexes.size() >= 1, "Invalid number of indexes");
            std::string indexExpr;
            const Plane2DIndexPattern& pip = static_cast<const Plane2DIndexPattern&> (ip);
            bool useParens = pip.getPattern1() != nullptr && pip.getPattern2() != nullptr;

            if (useParens) indexExpr += "(";

            if (pip.getPattern1() != nullptr)
                indexExpr += indexPattern2String(*pip.getPattern1(), *indexes[0]);

            if (useParens) indexExpr += ") + (";

            if (pip.getPattern2() != nullptr)
                indexExpr += indexPattern2String(*pip.getPattern2(), *indexes.back());

            if (useParens) indexExpr += ")";

            return indexExpr;
        }
        case IndexPatternType::Random1D:
        {
            CPPADCG_ASSERT_KNOWN(indexes.size() == 1, "Invalid number of indexes");
            const Random1DIndexPattern& rip = static_cast<const Random1DIndexPattern&> (ip);
            CPPADCG_ASSERT_KNOWN(!rip.getName().empty(), "Invalid name for array");
            return rip.getName() + "[" + (*indexes[0]->getName()) + "]";
        }
        case IndexPatternType::Random2D:
        {
            CPPADCG_ASSERT_KNOWN(indexes.size() == 2, "Invalid number of indexes");
            const Random2DIndexPattern& rip = static_cast<const Random2DIndexPattern&> (ip);
            CPPADCG_ASSERT_KNOWN(!rip.getName().empty(), "Invalid name for array");
            return rip.getName() + "[" + (*indexes[0]->getName()) + "][" + (*indexes[1]->getName()) + "]";
        }
        default:
            CPPADCG_ASSERT_UNKNOWN(false); // should never reach this
            return "";
    }
}
inline std::vector<size_t> ifBranchIterationRanges(const OperationNode<Base>* bScope,
                                                   IndexOperationNode<Base>*& iterationIndexOp) {
    CGOpCode bOp = bScope->getOperationType();

    if (bOp == CGOpCode::StartIf || bOp == CGOpCode::ElseIf) {
        OperationNode<Base>* cond = bScope->getArguments()[bOp == CGOpCode::StartIf ? 0 : 1].getOperation();
        CPPADCG_ASSERT_UNKNOWN(cond->getOperationType() == CGOpCode::IndexCondExpr);
        CPPADCG_ASSERT_UNKNOWN(cond->getArguments().size() == 1);
        CPPADCG_ASSERT_UNKNOWN(cond->getArguments()[0].getOperation() != nullptr);
        CPPADCG_ASSERT_UNKNOWN(cond->getArguments()[0].getOperation()->getOperationType() == CGOpCode::Index);
        iterationIndexOp = static_cast<IndexOperationNode<Base>*> (cond->getArguments()[0].getOperation());
        return cond->getInfo();

    } else {
        // else
        CPPADCG_ASSERT_UNKNOWN(bOp == CGOpCode::Else);

        std::vector<size_t> nonIterationRegions;
        OperationNode<Base>* ifBranch = bScope->getArguments()[0].getOperation();
        do {
            CGOpCode bbOp = ifBranch->getOperationType();
            OperationNode<Base>* cond = ifBranch->getArguments()[bbOp == CGOpCode::StartIf ? 0 : 1].getOperation();
            CPPADCG_ASSERT_UNKNOWN(cond->getOperationType() == CGOpCode::IndexCondExpr);
            CPPADCG_ASSERT_UNKNOWN(cond->getArguments().size() == 1);
            CPPADCG_ASSERT_UNKNOWN(cond->getArguments()[0].getOperation() != nullptr);
            CPPADCG_ASSERT_UNKNOWN(cond->getArguments()[0].getOperation()->getOperationType() == CGOpCode::Index);
            IndexOperationNode<Base>* indexOp = static_cast<IndexOperationNode<Base>*> (cond->getArguments()[0].getOperation());
            CPPADCG_ASSERT_UNKNOWN(iterationIndexOp == nullptr || iterationIndexOp == indexOp);
            iterationIndexOp = indexOp;

            combineOverlapingIterationRanges(nonIterationRegions, cond->getInfo());

            ifBranch = ifBranch->getArguments()[0].getOperation();
        } while (ifBranch->getOperationType() == CGOpCode::ElseIf);

        CPPADCG_ASSERT_UNKNOWN(iterationIndexOp != nullptr);

        // invert
        return invertIterationRanges(nonIterationRegions);
    }

}
 inline Plane2DIndexPattern(IndexPattern* pattern1,
                            IndexPattern* pattern2) :
     pattern1_(pattern1),
     pattern2_(pattern2) {
     CPPADCG_ASSERT_UNKNOWN(pattern1_ != nullptr || pattern2_ != nullptr);
 }
    inline size_t reserveArraySpace(const OperationNode<Base>& newArray) {
        size_t arraySize = newArray.getArguments().size();

        if (arraySize == 0)
            return 0; // nothing to do (no space required)

        std::set<size_t> blackList;
        const std::vector<Argument<Base> >& args = newArray.getArguments();
        for (size_t i = 0; i < args.size(); i++) {
            const OperationNode<Base>* argOp = args[i].getOperation();
            if (argOp != nullptr && argOp->getOperationType() == CGOpCode::ArrayElement) {
                const OperationNode<Base>& otherArray = *argOp->getArguments()[0].getOperation();
                CPPADCG_ASSERT_UNKNOWN(_varId[otherArray] > 0); // make sure it had already been assigned space
                size_t otherArrayStart = _varId[otherArray] - 1;
                size_t index = argOp->getInfo()[0];
                blackList.insert(otherArrayStart + index);
            }
        }

        /**
         * Find the best location for the new array
         */
        std::map<size_t, size_t>::reverse_iterator it;
        std::map<size_t, size_t>::reverse_iterator itBestFit = _freeArrayStartSpace.rend();
        size_t bestCommonValues = 0; // the number of values likely to be the same
        for (it = _freeArrayStartSpace.rbegin(); it != _freeArrayStartSpace.rend(); ++it) {
            size_t start = it->first;
            size_t end = it->second;
            size_t space = end - start + 1;
            if (space < arraySize) {
                continue;
            }

            std::set<size_t>::const_iterator itBlack = blackList.lower_bound(start);
            if (itBlack != blackList.end() && *itBlack <= end) {
                continue; // cannot use this space
            }

            //possible candidate
            if (itBestFit == _freeArrayStartSpace.rend()) {
                itBestFit = it;
            } else {
                size_t bestSpace = itBestFit->second - itBestFit->first + 1;

                size_t commonVals = 0;
                for (size_t i = 0; i < arraySize; i++) {
                    if (isSameArrayElement(_tmpArrayValues[start + i], args[i])) {
                        commonVals++;
                    }
                }

                if (space < bestSpace || commonVals > bestCommonValues) {
                    // better fit
                    itBestFit = it;
                    bestCommonValues = commonVals;
                    if (bestCommonValues == arraySize) {
                        break; // jackpot
                    }
                }
            }
        }

        size_t bestStart = std::numeric_limits<size_t>::max();
        if (itBestFit != _freeArrayStartSpace.rend()) {
            /**
             * Use available space
             */
            bestStart = itBestFit->first;
            size_t bestEnd = itBestFit->second;
            size_t bestSpace = bestEnd - bestStart + 1;
            _freeArrayStartSpace.erase(bestStart);
            if (bestSpace == arraySize) {
                // entire space 
                _freeArrayEndSpace.erase(bestEnd);
            } else {
                // some space left
                size_t newFreeStart = bestStart + arraySize;
                _freeArrayStartSpace[newFreeStart] = bestEnd;
                _freeArrayEndSpace.at(bestEnd) = newFreeStart;
            }

        } else {
            /**
             * no space available, need more
             */
            // check if there is some free space at the end
            std::map<size_t, size_t>::iterator itEnd;
            itEnd = _freeArrayEndSpace.find(_idArrayCount - 1 - 1); // IDcount - initialID - 1
            if (itEnd != _freeArrayEndSpace.end()) {
                // check if it can be used
                size_t lastSpotStart = itEnd->second;
                size_t lastSpotEnd = itEnd->first;
                size_t lastSpotSize = lastSpotEnd - lastSpotStart + 1;
                std::set<size_t>::const_iterator itBlack = blackList.lower_bound(lastSpotStart);
                if (itBlack == blackList.end()) {
                    // can use this space
                    _freeArrayEndSpace.erase(itEnd);
                    _freeArrayStartSpace.erase(lastSpotStart);

                    _idArrayCount += arraySize - lastSpotSize;
                    bestStart = lastSpotStart;
                }
            }

            if (bestStart == std::numeric_limits<size_t>::max()) {
                // brand new space
                size_t id = _idArrayCount;
                _idArrayCount += arraySize;
                bestStart = id - 1;
            }
        }

        for (size_t i = 0; i < arraySize; i++) {
            _tmpArrayValues[bestStart + i] = &args[i];
        }

        CPPADCG_ASSERT_UNKNOWN(_freeArrayStartSpace.size() == _freeArrayEndSpace.size());

        return bestStart;
    }