/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * aux.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 */

/**
 *  \file src/analysis/utils.h
 *  \brief Various reasoning related utilities
 */

#include "utils.h"
#include <boost/format.hpp>

using namespace std;

namespace xreate { namespace analysis {

std::list<std::string>
compile(const Expression &e){
    list<string> result;

    switch (e.op) {
        case Operator::CALL: {
            if(!e.operands.size()){
                result.push_back(e.getValueString());
                break;
            }

            std::list<list<string>> operands;
            std::transform(e.operands.begin(), e.operands.end(), std::inserter(operands, operands.begin()),
                    [](const Expression &e) {
                        return compile(e);
                    });

            list<string> &&operands_ = multiplyLists(std::move(operands));
            result.push_back(boost::str(boost::format("%1%(%2%)") % (e.getValueString()) % (boost::algorithm::join(operands_, ", "))));
            break;
        }

        case Operator::NEG: {
            assert(e.operands.size() == 1);

            const Expression &op = e.operands.at(0);
            list<string> &&rawOp = compile(op);

            assert(rawOp.size() == 1);
            result.push_back((boost::format("not %1%")%(rawOp.front())).str());
            break;
        };

        case Operator::INVALID: {
            switch (e.__state) {
                case Expression::IDENT:
                    result.push_back(e.getValueString());
                    break;

                case Expression::NUMBER:
                    result.push_back(to_string(e.getValueDouble()));
                    break;

                default:
                    assert(true);
            }
            break;
        }

        default: break;
    }

    assert(result.size());
    return result;
}

list<string>
multiplyLists(list<list<string>> &&lists) {
    typedef list<string> StringList;
    assert(lists.size());
    StringList result(*lists.begin());
    lists.pop_front();

    boost::format concat("%s, %s");
    for (StringList &list: lists) {
        StringList::const_iterator end = result.end();
        for (StringList::iterator expr1I = result.begin(); expr1I != end; ++expr1I) {
            if (list.size() == 0) continue;

            StringList::const_iterator expr2I = list.begin();
            for (int expr2No = 0, size = list.size() - 1; expr2No < size; ++expr2No, ++expr1I)
                result.push_back(str(concat %(*expr1I) %(*expr2I)));

            *expr1I = str(concat %(*expr1I) %(*expr2I));
        }
    }

    return result;
}

std::list<std::string>
compileNeg(const Expression &e){
    list<string> result;
    switch (e.op) {
        case Operator::IMPL: {
            assert(e.__state == Expression::COMPOUND);
            assert(e.operands.size() == 2);
            list<string> operands1 = compile(e.operands.at(0));
            list<string> operands2 = compile(e.operands.at(1));

            boost::format formatNeg("%1%, not %2%");
            for (const auto &op1: operands1)
                for (const auto &op2: operands2) {
                    result.push_back(boost::str(formatNeg %(op1) % (op2)));
                }
            break;
        }
        case Operator::NEG: {
            assert(e.operands.size() == 1);

            const Expression &op = e.operands.at(0);
            list<string> &&rawOp = compile(op);

            assert(rawOp.size() == 1);
            result.push_back(rawOp.front());
            break;
        };

        default:
            assert(true);
    }

    return result;
}

//NOTE: Any changes should be reflected in ParseImplAtom<SymbolPacked>,
// ParseImplAtom<SymbolNode>
class VisitorFormatSymbol: public boost::static_visitor<boost::format> {
public:

    boost::format operator()(const SymbolPacked& node) const {
        boost::format formatSymbNamed("s(%1%,%2%,%3%)");
        return formatSymbNamed % node.identifier % node.version  % node.scope ;
    }

    boost::format operator()(const SymbolAnonymous& node) const {
        boost::format formatSymbAnonymous("a(%1%)");
        return formatSymbAnonymous % node.id;
    }
};

boost::format writeSymbolNode(const SymbolNode& symbol){
    return boost::apply_visitor(VisitorFormatSymbol(), symbol);
}

}} //end of xreate::analysis


