/*
 * fcsp.cpp
 *
 *  Created on: Dec 16, 2013
 *      Author: dmitry
 */
#include <algorithm>
#include <numeric>
#include <iomanip>
#include <set>
#include <boost/graph/vf2_sub_graph_iso.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/visitors.hpp>

#include "conv.hpp"
#include "ctab.hpp"
#include "descriptors.hpp"
#include "fcsp.hpp"
#include "log.hpp"

enum { NON_PASSABLE = 10000 };
using namespace std;
using namespace boost;

void printJsArray(vector<int>& v, ostream& out)
{
    out << "[";
    sort(v.begin(), v.end());
    v.erase(unique(v.begin(), v.end()), v.end());
    for(size_t i=0;i<v.size();i++)
    {
        if(i != 0) out << ", ";
        out << v[i];
    }
    out << "]";
}

bool is_exclusive_dc(int dc)
{
    return dc == 45 || dc == 46; // these are impassable if one is part of chain
}

// Select 1-s from bool matrix so that
// there is at least one selected per row
// and no more then one per column
// Input:
// matrix - bool matrix
// selections : row idx --> col idx
// cur - number of currently matching row
bool pickChoices(const vector<bool>& matrix, size_t rows, size_t cols, vector<size_t>& selections, size_t cur=0)
{
    if(cur == rows)
        return true;
    for(size_t i=0; i<cols; i++)
    {
        if(matrix[cur*cols + i])
        {
            selections[cur] = i;
            auto end = selections.begin()+cur;
            // not chosen before
            if(find(selections.begin(), selections.begin()+cur, i) == end)
            {
                if(pickChoices(matrix, rows, cols, selections, cur+1))
                    return true;
                else
                    selections[cur] = -1; // reset and continue
            }
        }
    }
    return false;
}

struct TrackPath: public default_bfs_visitor {
    ChemGraph& g;
    vector<pair<vd, int>>& dcs;
    ChemGraph::vertex_descriptor start, tgt;
    bool pass_4546;
    TrackPath(ChemGraph& graph, ChemGraph::vertex_descriptor s, ChemGraph::vertex_descriptor t,
        vector<pair<vd, int>>& dcsArr) :
        g(graph), dcs(dcsArr), start(s), tgt(t)
    {
        auto sdc = *find_if(dcs.begin(), dcs.end(), [&](pair<vd,int> p){
            return p.first == start;
        });
        auto edc = *find_if(dcs.begin(), dcs.end(), [&](pair<vd,int> p){
            return p.first == tgt;
        });
        pass_4546 = sdc.second != 45 && sdc.second != 46 && edc.second != 45 && edc.second != 46;
        LOG(TRACE) << (pass_4546 ? "Passing" :"Not passing") <<" DC 45-46 while going "
            << sdc.second << "-->"<< edc.second << endline;
    }

    template<typename Vertex>
    void initialize_vertex(Vertex v, const ChemGraph&) const
    {
        g[v].path = 0;
    }

    template<typename Edge>
    void tree_edge(Edge e, const ChemGraph&) const
    {
        auto d = target(e, g);
        auto s = source(e, g);
        //cout << s << "-->" << d << endline;
        if (g[d].inAromaCycle && d != tgt) //all paths  of aromatic cycle  are inpassable
            g[d].path = NON_PASSABLE;
        else if (g[d].inAromaCycle && g[s].inAromaCycle)
            g[d].path = NON_PASSABLE;
        else if (g[d].code != C && d != tgt)
            g[d].path = NON_PASSABLE;
        else if(d != tgt && !pass_4546 && g[d].code == C)
        {
            bool hit4546 = find_if(dcs.begin(), dcs.end(), [d](pair<vd,int> p){
                return p.first == d && (p.second == 45 || p.second == 46);
            }) != dcs.end();

            if(hit4546)
            {
                auto sdc = *find_if(dcs.begin(), dcs.end(), [&](pair<vd,int> p){
                    return p.first == start;
                });
                auto edc = *find_if(dcs.begin(), dcs.end(), [&](pair<vd,int> p){
                    return p.first == tgt;
                });
                LOG(DEBUG) << "Hit non-passable DC 45/46 while going "
                    << sdc.second << "-->" << edc.second << endline;
                g[d].path = NON_PASSABLE;
            }
            else
                g[d].path = g[s].path + 1;
        }
        else
            g[d].path = g[s].path + 1;
    }
};

// callback for VF2 algorithm
struct CollectAsVectors{
    ChemGraph& pattern;
    vector<vector<size_t>>& mappings; // mapped atoms in the bigger graph

    CollectAsVectors(ChemGraph& pat, vector<vector<size_t>>& _mappings):pattern(pat), mappings(_mappings){}
    template <typename CorrespondenceMap1To2,
          typename CorrespondenceMap2To1>
    bool operator()(CorrespondenceMap1To2 f, CorrespondenceMap2To1 g) const
    {
        auto vtx = vertices(pattern);
        vector<size_t> in_big;
        for(auto p=vtx.first; p != vtx.second; p++)
        {
            in_big.push_back(get(f, *p));
        }
        using std::move;
        mappings.push_back(move(in_big));
        return true;
    }
};

int singleCount(ChemGraph& graph, ChemGraph::vertex_descriptor vertex)
{
    auto edges = out_edges(vertex, graph);
    int cnt = 0;
    for (auto p = edges.first; p != edges.second; p++)
    {
        if (graph[*p].type == 1)
            cnt++;
    }
    return cnt;
}

pair<int,int> multiCount(ChemGraph& graph, ChemGraph::vertex_descriptor vertex)
{
    auto edges = out_edges(vertex, graph);
    int dual = 0, tripple = 0;
    for (auto p = edges.first; p != edges.second; p++)
    {
        if (graph[*p].type == 2)
            dual++;
        else if (graph[*p].type == 3)
            tripple++;
    }
    return make_pair(dual, tripple);
}

struct FCSP::Impl{
    Impl(FCSPOptions opts) :
        order1(std::move(opts.first)), order2(std::move(opts.second)),
        repls(std::move(opts.replacements)),
        long41(opts.long41), format(opts.format){}

    void load(istream& inp)
    {
        CTab tab = readMol(inp);
        graph = toGraph(tab);
        auto comp = connectedComponents(graph);
    }
    // Очистить все переменные состояния кодировщика
    void clear()
    {
        outPieces.clear();
        outPiecesCycle.clear();
        dcs.clear();
        dcsAtoms.clear();
        reserved_dcs.clear();
        cycles.clear();
    }

    void sortDCs()
    {
        // filter out things that got reserved
        auto before = dcs.size();
        dcs.erase(remove_if(dcs.begin(), dcs.end(), [&](const pair<vd, int>& a){
            auto p = reserved_dcs.find(a.first);
            // LOG(FATAL) << "> " << (p != reserved_dcs.end()) << endline;
            return p != reserved_dcs.end() && p->second != a.second;
        }), dcs.end());
        LOG(INFO) << "Filtered " << before - dcs.size() << " DCs in favor of monolithic patterns."<< endline;
        sort(dcs.begin(), dcs.end(), [](const pair<vd, int>& a, const pair<vd, int>& b){
            return a.second < b.second;
        });
        LOG(INFO) << "Found DCs:" << endline;
        for (auto& dc : dcs)
        {
            LOG(INFO) << dc.first << " -DC-> " << dc.second << endline;
        }
        LOG(INFO) << endline;
    }

    void process(ostream& out, string filename)
    {
        clear(); // clear state
        addHydrogen(graph);
        locatePiElectrons();
        locateCycles(); //adds cyclic DCs
        locateDCs(false);
        locateIrregular();
        sortDCs();
        cyclic(out);
        linear(out);
        locateDCs(true); // REPL-only DCs - 44 so far
        sortDCs();
        replacement(out);
        outputWhole(out, filename);
    }

    void dumpGraph(ostream& out)
    {
        ::dumpGraph(graph, out);
    }

    void outputPieceCycle(string code, vector<int>& atoms)
    {
        outputPiece_(code, atoms, true);
    }

    void outputPiece(string code, vector<int>& atoms)
    {
        outputPiece_(code, atoms, false);
    }

    void outputPiece_(string code, vector<int>& atoms, bool cycle)
    {
        if(format == FCSPFMT::JSON)
        {
            stringstream s;
            s << "{" << "\"code\" : \""<<code<<"\",";
            s << "\"place\": ";
            printJsArray(atoms, s);
            s << "}";
            if(cycle)
                outPiecesCycle.push_back(s.str());
            else
                outPieces.push_back(s.str());
        }
        else if(format == FCSPFMT::CSV || format == FCSPFMT::TXT)
        {
            if(cycle)
                outPiecesCycle.push_back(code);
            else
                outPieces.push_back(code);
        }
    }

    void outputWhole(ostream& out, string filename)
    {
        sort(outPiecesCycle.begin(), outPiecesCycle.end());
        sort(outPieces.begin(), outPieces.end());
        outPieces.insert(outPieces.begin(), outPiecesCycle.begin(), outPiecesCycle.end());
        if(format == FCSPFMT::JSON)
        {
            out << "[";
            for(size_t i=0; i<outPieces.size();i++)
            {
                if(i != 0)
                    out << ", ";
                out << outPieces[i];
            }
            out << "]";
            out << endline;
        }
        else if(format == FCSPFMT::CSV || format == FCSPFMT::TXT)
        {
            if(format == FCSPFMT::CSV)
                out << filename << ';';
            for(size_t i=0; i<outPieces.size();i++)
            {
                if(i != 0)
                    out << " ";
                out << outPieces[i];
            }
            out << endline;
        }
    }

    void locatePiElectrons()
    {
        auto vrtx = vertices(graph);
        for (auto i = vrtx.first; i != vrtx.second; i++)
        {
            //pre-calculate per-atom properties
            int valency = getValence(graph, *i);
            graph[*i].valence = valency;
            auto dual_tripple = multiCount(graph, *i);
            int piE = countPiElectrons(graph[*i].code, valency, dual_tripple.first, dual_tripple.second);
            graph[*i].piE = piE > 0 ? piE : 0;
        }
    }

    void locateDCs(bool replOnly)
    {
        auto vrtx = vertices(graph);
        for (auto i = vrtx.first; i != vrtx.second; i++)
        {
            int valency = graph[*i].valence;
            auto edges = out_edges(*i, graph);
            LevelOne t{graph[*i].code, valency, 0};
            auto range = equal_range(order1.begin(), order1.end(), t);
            if(!replOnly) // skip level-1 DCs and 45-46 for repl-only DCs
            {
                for (auto j = range.first; j != range.second; ++j)
                {
                    dcs.emplace_back(*i, j->dc);
                }
                if(graph[*i].code == C && !graph[*i].inAromaCycle)
                {
                    // check for 45 & 46
                    for (auto p = edges.first; p != edges.second; p++)
                    {
                        auto tgt = target(*p, graph);
                        if (graph[*p].type == 2 && graph[tgt].code == C && !graph[tgt].inAromaCycle)
                        {
                            auto tgt_edges = out_edges(tgt, graph);
                            auto cnt = count_if(tgt_edges.first, tgt_edges.second, [&](ed edge){
                                if(graph[edge].type == 2){
                                    auto t2 = target(edge, graph);
                                    if(graph[t2].code == C && !graph[t2].inAromaCycle)
                                        return true;
                                }
                                return false;
                            });
                            if(cnt == 2) // 2 double links both with C - 45 DC
                            {
                                dcs.emplace_back(*i, 45);
                            }
                            else // only one double link
                            {
                                dcs.emplace_back(*i, 46);
                            }
                        }
                        else if (graph[*p].type == 3 && graph[tgt].code == C && !graph[tgt].inAromaCycle)
                        {
                            dcs.emplace_back(*i, 45);
                        }
                    }
                }
            }
            // select a range of DCs pattern with center matching this atom
            auto range2 = make_pair(order2.begin(), order2.end());
            for (auto j = range2.first; j != range2.second; j++)
            {
                if(j->replOnly != replOnly) // can't use during this stage
                    continue;
                if (edges.second - edges.first < (int)j->bonds.size())
                    continue;
                if (!j->center.matches(graph[*i].code))
                    continue;
                if (j->valence != valency)
                    continue;
                LOG(TRACE) << "Candidate DC "<< j->dc <<" CENTER " << j->center.symbol() << " VALENCE "<< valency << endline;

                vector<int> atoms; // atoms in this center
                size_t cand_bnds = edges.second - edges.first;
                size_t smpl_bnds = j->bonds.size();
                // Put ones for combinations that match. A row per edge in a DC pattern (sample).
                // Then we need to pick one in each row, if at least one row is all zeros - no match
                // Same DC may happen twice in the same atom, but we don't accomodate for that (for now)
                vector<bool> mappings(cand_bnds*smpl_bnds);
                LOG(TRACE) << "MAPPING:" <<endline;
                LOG(TRACE) << "  ";
                for(size_t k=0; k<cand_bnds; k++)
                {
                    auto e = (edges.first+k);
                    auto t = target(*e, graph);
                    LOG(TRACE) << setw(2) << graph[t].code.symbol();
                }
                for(size_t p=0; p<smpl_bnds; p++)
                {
                    LOG(TRACE) << endline << setw(2) << j->bonds[p].atom.symbol();
                    for(size_t q=0; q<cand_bnds; q++)
                    {
                        auto e = edges.first + q;
                        if(graph[*e].type == j->bonds[p].bondType)
                        {
                            auto t = target(*e, graph);
                            if(j->bonds[p].atom.matches(graph[t].code))
                                mappings[p*cand_bnds + q] = true;
                        }
                        LOG(TRACE) << setw(2) << mappings[p*cand_bnds + q];
                    }
                }
                LOG(TRACE) << endline;
                vector<size_t> found_mapping(smpl_bnds); // sample idx --> candidate idx
                if(pickChoices(mappings, smpl_bnds, cand_bnds, found_mapping))
                {
                    for(auto idx : found_mapping)
                    {
                        // get atom by index of edge around this atom
                        auto t = target(*(edges.first + idx), graph);
                        atoms.push_back(t);
                    }
                    dcs.emplace_back(*i, j->dc);
                    dcsAtoms.insert(make_pair(*i, atoms));
                    // only assign DCs to reserve once
                    if(j->monolith && reserved_dcs.find(*i) == reserved_dcs.end())
                    {
                        LOG(DEBUG) << "Reserved for DC "<< j->dc << " "<< atoms.size() + 1 <<" atoms"<<endline;
                        //reserve atoms that belong to this DC
                        reserved_dcs[*i] = j->dc;
                        for(auto v : atoms)
                        {
                            if(graph[v].code != C) //FIXME: should be more sensible
                                reserved_dcs[v] = j->dc;
                        }
                    }
                }
            }
        }
    }

    template<class T>
    static const T& chainAt(const vector<T>& chain, int idx)
    {
        while (idx < 0)
            idx += (int)chain.size();
        while (idx >= (int)chain.size())
            idx -= (int)chain.size();
        return chain[idx];
    }

    static string keyatom(ChemGraph& g, vd v)
    {
        //numbers mean at least x links of given type
        //val == 0 - do not care
        struct Entry {
            char symbol;
            Code code;
            int valence;
            int singleLinks;
            int dualLinks;
            int trippleLinks;
        };
        Entry table[] = {
            { 'M', N, 0, 1, 1, 0 },
            { 'N', N, 0, 2, 0, 0 },
            { 'Q', O, 2, 0, 0, 0 },
            { 'R', O, 3, 0, 0, 0 },
            { 'T', S, 0, 1, 1, 0 },
            { 'S', S, 2, 2, 0, 0 }
        };
        for (auto& e : table)
        {
            if (g[v].code == e.code)
            {
                if (e.valence && g[v].valence != e.valence)
                    continue;
                auto dt = multiCount(g, v);
                auto s = singleCount(g, v);
                if (e.singleLinks && e.singleLinks > s)
                    continue; //not enough links
                if (e.dualLinks && e.dualLinks > dt.first)
                    continue;
                if (e.trippleLinks && e.trippleLinks > dt.second)
                    continue;
                return string(1, e.symbol);
            }
        }
        if (heteroatom(g[v]))
            return g[v].code.symbol();
        else
            return string();
    }

    static bool heteroatom(const AtomVertex& atom)
    {
        return !atom.code.matches(C) && !atom.code.matches(H);
    }

    void locateCycles()
    {
        cycles = minimalCycleBasis(graph);
        for (auto& ic : cycles)
        {
            auto& vc = ic.chain;
            ic.markAromatic(graph);
            if(ic.aromatic()) // aromatic specific DCs
            {
                for(auto n : vc)
                {
                    //any atom in aromatic cycle
                    dcs.emplace_back(n, 33);
                    //hetero-atom in aromatic cycle - DC 34
                    if (!graph[n].code.matches(C))
                        dcs.emplace_back(n, 34);
                    auto idx = find(vc.begin(), vc.end(), n) - vc.begin();
                    assert(idx != (int)vc.size());
                    if (heteroatom(graph[chainAt(vc, idx - 1)])
                        || heteroatom(graph[chainAt(vc, idx + 1)]))
                    {
                        dcs.emplace_back(n, 35);
                    }
                    else if (heteroatom(graph[chainAt(vc, idx - 2)])
                        || heteroatom(graph[chainAt(vc, idx + 2)]))
                    {
                        dcs.emplace_back(n, 36);
                    }
                    else if (heteroatom(graph[chainAt(vc, idx - 3)])
                        || heteroatom(graph[chainAt(vc, idx + 3)]))
                    {
                        dcs.emplace_back(n, 37);
                    }
                    //TODO: DC 40 - need to handle charges carefully to test charged atoms
                }
            }
        }
    }

    void locateIrregular() //  TODO: rewrite
    {
        /*for (auto& r : irregularTempl)
        {
            vector<vector<pair<size_t, size_t>>> mappings;
            auto& g = graph;
            ullmann_all(r.piece, graph, [&r, &g](ChemGraph::vertex_descriptor a, ChemGraph::vertex_descriptor b){
                return r.piece[a].code == g[b].code;
            }, [&r, &g](ChemGraph::edge_descriptor a, ChemGraph::edge_descriptor b){
                return r.piece[a].type == g[b].type;
            }, mappings);
        }*/
    }

    void addDescriptorAtoms(vector<int>& fragment, vd dc)
    {
        if(dcsAtoms.find(dc) != dcsAtoms.end()){
            fragment.insert(fragment.end(), dcsAtoms[dc].begin(), dcsAtoms[dc].end());
        }
    }

    template<class Fn>
    void applyPath(vd start, vd end, Fn&& fn)
    {
        vd current = end;
        fn(current);
        while (current != start)
        {
            //cout << graph[current].path << " -->" << endline;
            auto nearby = adjacent_vertices(current, graph);
            auto i = nearby.first;
            for (; i != nearby.second; i++)
            {
                //cout << graph[*i].path << ".." ;
                if (graph[*i].path == graph[current].path - 1)
                {
                    current = *i;
                    fn(current);
                    break;
                }
            }
            if (i == nearby.second)
                break;
        }
    }

    void linear(ostream& out)
    {
        queue<vd> buf;
        // DCs are sorted as a[0] < .. < a[n-1]
        for (size_t i = 0; i < dcs.size(); i++)
        for (size_t j = i  + 1; j < dcs.size(); j++)
        {
            vd start = dcs[i].first;
            vd end = dcs[j].first;
            int start_dc = dcs[i].second;
            int end_dc = dcs[j].second;
            breadth_first_search(graph, start, buf,
                TrackPath(graph, start, end, dcs), get(&AtomVertex::color, graph));
            if (graph[end].path && graph[end].path < NON_PASSABLE)
            {
                bool coupled = true; //0-length path is therefore coupled (FIXME: check PI el-s too)
                auto &g = graph;
                vector<int> fragment;
                if(start_dc == 41) // check only the first
                {
                    bool check = true;
                    applyPath(start, end, [g, end, &check, &coupled, &fragment](vd v){
                        if(check)
                        {
                            if (v != end && g[v].code.matches(C) && g[v].piE == 0)
                                coupled = false;
                            check = false;
                        }
                        fragment.push_back((int)v);
                    });
                }
                else
                {
                    applyPath(start, end, [g, end, &coupled, &fragment](vd v){
                        if (v != end && g[v].code.matches(C) && g[v].piE == 0)
                            coupled = false;
                        fragment.push_back((int)v);
                    });
                }
                int len = graph[end].path - 1;
                //SPECIAL CASE - TODO verifiy correctness
                if(len == 0)
                    coupled = true;
                if (len == 0 && is_exclusive_dc(start_dc) && start_dc == end_dc)
                {
                    if(g[edge(start, end, g).first].type > 1) // connected with non-single link
                    {
                        continue; // skip output - this is self-referental
                    }
                }
                // SPECIAL CASE - "The long 41" rule
                if(start_dc == 41 && long41)
                    len += 1;
                if(end_dc == 41 && long41)
                    len += 1;
                stringstream buffer;
                buffer << setfill('0') << setw(2) << start_dc
                    << setfill('0') << setw(2) << len
                    << setfill('0') << setw(2) << end_dc
                    << (coupled ? 1 : 0);
                addDescriptorAtoms(fragment, dcs[i].first);
                addDescriptorAtoms(fragment, dcs[j].first);
                outputPiece(buffer.str(), fragment);
            }
        }
    }

    struct Edge{
        pair<vd, vd> e;
        int cycNum; //e принадлежит циклу cycles[cycNum]
        Edge(pair<vd, vd> e_, int cyc) :
            e(e_), cycNum(cyc){}
        bool operator<(const Edge& rhs)const
        {
            return e < rhs.e;
        }
    };

    //Строим запись "головы" двигаясь по огибающей (common) в обе стороны
    string encodeHead(int firstCycle, vector<vd>& commonCh, vector<Edge>& common, int totalCycles)
    {
        //Выбор опорного атома из стартового цикла
        auto& firstChain = cycles[firstCycle].chain;
        auto firstIdx = find_if(commonCh.begin(), commonCh.end(), [&firstChain](int v){
            return find(firstChain.begin(), firstChain.end(), v) != firstChain.end();
        });
        auto fIdx = firstIdx - commonCh.begin();
        // обход вперед/назад
        auto fwd = encodeCycle(1, commonCh, fIdx, firstCycle, common, totalCycles);
        auto bwd = encodeCycle(-1, commonCh, fIdx, firstCycle, common, totalCycles);
        return fwd < bwd ? fwd : bwd;
    }

    string encodeCycle(int dir, vector<vd>& commonCh, int fIdx, int cycNum, vector<Edge>& common, int totalCycles)
    {
        stringstream cyclic_out;
        int edgeNum = 0; // on the most recent cycle
        int prevEdgeNum = 0; //edge count tracked on previous cycle
        size_t cycCount = 0; //first 2 are output as is, 3rd, 4th and so on need prefixes
        //for (auto& p : common)
        //    cout << p.e.first << ":" << p.e.second << endline;
        auto v1 = chainAt(commonCh, fIdx);
        for (int j = dir; ; j+= dir)
        {
            auto v2 = chainAt(commonCh, fIdx + j);
            auto edge = v1 < v2 ? make_pair(v1, v2) : make_pair(v2, v1);
            v1 = v2;
            auto p = equal_range(common.begin(), common.end(), Edge(edge, 0));
            assert(p.first != p.second);
            //cout << "Go with " << p.first->e.first << ":" << p.first->e.second << endline;
            edgeNum++;
            if (cycNum != p.first->cycNum)
            {
                //cout << "Cycle!" << endline;
                static string tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                cycCount++;
                if (cycCount > 2)
                {
                    assert(prevEdgeNum > 0 && prevEdgeNum < 20); //TODO: error on this
                    cyclic_out << setw(1) << tab[prevEdgeNum-1]
                        << setw(1) << cycles[cycNum].edges.size();
                }
                else
                    cyclic_out << setw(1) << cycles[cycNum].edges.size();
                cycNum = p.first->cycNum;
                prevEdgeNum = edgeNum;
                edgeNum = 0;
                if (cycCount == totalCycles) // all encoded
                    break;
            }
        }
        return cyclic_out.str();
    }

    int pickNonKeyAtom(int dir, int fChain, int firstCycle, vector<vd>& commonCh, vector<Edge>& common)
    {
        for (int k = 0, j = fChain; k < (int)commonCh.size(); k++, j += dir)
        {
            auto v = chainAt(commonCh, j);
            auto v2 = chainAt(commonCh, j + dir);
            auto p = v < v2 ? make_pair(v, v2) : make_pair(v2, v);
            auto e = Edge(p, 0);
            auto r = equal_range(common.begin(), common.end(), e);
            assert(r.first != r.second);
            if (r.first->cycNum != firstCycle)
            {
                return j - dir;//step back
            }
        }
        assert(false);
        return 0;
    }

    string encodeHeteroAtoms(int dir, int start, vector<vd>& commonCh)
    {
        stringstream s;
        for (int i = 0, j = start; i < commonCh.size(); i++, j += dir)
        {
            auto v = chainAt(commonCh, j);
            string k = keyatom(graph, v);
            if (!k.empty())
            {
                s << k << i + 1;
            }
        }
        return s.str();
    }

    //Строим запись "хвоста" по стартовому циклу, двигаясь по огибающей (common) в обе стороны
    string encodeTail(int firstCycle, vector<vd>& commonCh, vector<Edge>& common, int totalCycles)
    {
        auto& firstChain = cycles[firstCycle].chain;
        auto firstIdx = find_if(commonCh.begin(), commonCh.end(), [&firstChain](int v){
            return find(firstChain.begin(), firstChain.end(), v) != firstChain.end();
        });
        auto fi = firstIdx - commonCh.begin();
        //Идем в обе стороны и находим последние элементы в нашем цикле
        int firstV, secondV;
        //Правило: ключевые атомы (принадлежащие нескольким циклам) должны иметь наибольший номер
        firstV = pickNonKeyAtom(1, fi, firstCycle, commonCh, common);
        secondV = pickNonKeyAtom(-1, fi, firstCycle, commonCh, common);
        string variant[2];
        //firstV первый неключевой атом в "+" сторону, значит нумеруем в обратную
        variant[0] = encodeHeteroAtoms(-1, firstV, commonCh);
        //тоже для secondV
        variant[1] = encodeHeteroAtoms(1, secondV, commonCh);
        sort(variant, variant + 2);
        return variant[0];
    }

    //копирует ccv, тк будет не однакратно сортирован
    void encodeOnePolyCycle(vector<int> ccv)
    {
        //элементарные циклы кодируются отдельно
        if (ccv.size() == 1)
            return;
        vector<int> intercounts(cycles.size()); //кол-во пересечний с другими циклами
        for (auto n : ccv)
        {
            int c = 0;
            for (auto n2 : ccv)
            {
                if (intermap[n*cycles.size() + n2])
                    c++;
            }
            intercounts[n] = c;
        }
        //Выбираем стартовый цикл - минимальный по числу пересечений (крайний), минимальный по числу элементов
        //ищем сразу 2 стартовых цикла, на случай, где они одинково хорошо подходят
        auto& cys = cycles;
        partial_sort(ccv.begin(), ccv.begin() + 2, ccv.end(), [&intercounts, &cys](int a, int b){
            int ia = intercounts[a];
            int ib = intercounts[b];
            return ia < ib || (ia == ib && cys[a].edges.size() < cys[b].edges.size());
        });
        auto start = ccv.begin();
        //Строим огибающий цикл (ребра)
        //в огибающей записывается номер цикла, которому принадлежит ребро
        vector<Edge> common;
        vector<size_t> idxs(ccv.size()); //idxs[i] --> cycles[ccv[i]][*]
        //алгоритм объединения N множест ребер
        //с исключением по предикату "принадлежит более чем одному множеству"
        for (;;)
        {
            //which edge to pick
            pair<vd, vd> m_edge(-1, -1);
            size_t smallestCCV = 0; //number of ccv entry having the smalles edge so far
            for (size_t i = 0; i < idxs.size(); i++)
            {
                auto n_edge = idxs[i];
                if (n_edge >= cys[ccv[i]].edges.size())
                    continue;
                auto edge = cys[ccv[i]].edges[n_edge];
                if (m_edge.first == (size_t)-1 || edge < m_edge)
                {
                    m_edge = edge;
                    smallestCCV = i;
                }
            }
            if (m_edge.first == (size_t)-1)
                break;
            auto i = idxs[smallestCCV];
            auto& c = cys[ccv[smallestCCV]];
            idxs[smallestCCV]++;
            //проверка - c[i] должен принадлежать только одному эл. циклу
            int matches = 0;
            for (auto x : ccv)
            {
                auto er = equal_range(cys[x].edges.begin(), cys[x].edges.end(), c.edges[i]);
                if (er.first != er.second)
                    matches++;
            }
            if (matches == 1 && (common.empty() || common.back().e != c.edges[i]))
                common.push_back(Edge(c.edges[i], ccv[smallestCCV]));
            assert(is_sorted(common.begin(), common.end()));
        }
        LOG(DEBUG) << "COMMON EDGE SET:";
        for(auto e : common)
            LOG(DEBUG) << e.e << " ";
        LOG(DEBUG) << endline;
        auto commonCh = cycleToChain(common, [](const Edge& e){ return e.e; });
        string head = encodeHead(*start, commonCh, common, ccv.size());
        string head2 = head;
        // еще один стартовый цикл (если одинакового размера)
        if (cycles[*start].edges.size() == cycles[*(start + 1)].edges.size())
        {
            head2 = encodeHead(*(start + 1), commonCh, common, ccv.size());
        }
        head = head < head2 ? head : head2;
        //Суммируем Пи электроны по огибающей
        auto range = vertices(graph);
        auto sz = range.second - range.first;
        vector<bool> visited(sz);
        int piE = 0;
        for (int v : commonCh)
        {
            piE += graph[v].piE;
        }
        bool aromatic = (piE - 2) % 4 == 0;
        //Кодируем "хвост"
        //Выбираем новый цикл - минимальный по числу пересечений (крайний), _максимальный_ по числу элементов
        partial_sort(ccv.begin(), ccv.begin() + 2, ccv.end(), [&intercounts, &cys](int a, int b){
            int ia = intercounts[a];
            int ib = intercounts[b];
            return ia < ib || (ia == ib && cys[a].edges.size() > cys[b].edges.size());
        });
        start = ccv.begin();
        string tail = encodeTail(*start, commonCh, common, ccv.size());
        string tail2 = tail;
        // еще один стартовый цикл (если одинакового размера)
        if (cycles[*start].edges.size() == cycles[*(start + 1)].edges.size())
            tail2 = encodeTail(*(start + 1), commonCh, common, ccv.size());
        if (tail > tail2)
            tail = tail2;
        stringstream buffer;
        vector<int> fragment;

        buffer << head;
        buffer << "," << setfill('0') << setw(2) << (aromatic ? piE : 0);
        buffer << tail;
        for(auto cc : ccv){
            fragment.insert(fragment.end(), cycles[cc].chain.begin(), cycles[cc].chain.end());
        }
        outputPieceCycle(buffer.str(), fragment);
    }

    void recursePoly(vector<int>& poly, map<int, vector<int>>& adj_list, set<vector<int>>& used)
    {
        auto e = poly.back();
        for (auto x : adj_list[e])
        {
            {
                if (find(poly.begin(), poly.end(), x) != poly.end())
                    continue;
                assert(intermap[x + e*cycles.size()]);
                poly.push_back(x);
                if (poly.size() > 1)
                {
                    auto cp = poly;
                    sort(cp.begin(), cp.end());
                    if (used.find(cp) == used.end())
                    {
                        encodeOnePolyCycle(cp);
                        using std::move;
                        used.insert(move(cp));
                    }
                }
                //добавить еще один цикл
                if (poly.size() < adj_list.size())
                    recursePoly(poly, adj_list, used);
                poly.pop_back();
            }
        }
    }

    void encodePolyCycles(vector<int>& ccv)
    {
        map<int, vector<int>> adj_list; //ccv[i] --> adj_list[i]
        for (auto x : ccv)
        {
            vector<int> neib;
            for (auto y : ccv)
            {
                if (y != x && intermap[y*cycles.size() + x])
                {
                    neib.push_back(y);
                }
            }
            using std::move;
            adj_list.insert(make_pair(x, move(neib)));
        }
        vector<int> poly;
        set<vector<int>> used;
        for (auto x : ccv)
        {
            poly.push_back(x);
            recursePoly(poly, adj_list, used);
            poly.pop_back();
            assert(poly.size() == 0);
        }
    }

    void cyclic(ostream& out)
    {
        // Кодирование простых циклов
        for (auto cyc : cycles)
        {
            auto& ch = cyc.chain;
            int piE = 0;
            // NEW RULE - always output piE for singleton cycles and coupling linked systems
            for (vd v : ch)
                piE += graph[v].piE;
            vector<pair<string, vd>> hatoms;
            for (vd v : ch)
            {
                string s = keyatom(graph, v);
                if (!s.empty())
                {
                    hatoms.emplace_back(s, v);
                }
            }
            auto pivot = min_element(hatoms.begin(), hatoms.end(), [](const pair<string, int> & lhs, const pair<string, int> &rhs){
                return lhs.first < rhs.first;
            });
            stringstream buffer;
            vector<int> fragment;
            fragment.insert(fragment.end(), ch.begin(), ch.end());
            buffer << setfill('0') << setw(1) << cyc.edges.size()
                << ","
                << setfill('0') << setw(2) << piE;
            bool heterocycle = pivot != hatoms.end();
            if (heterocycle)
            {
                string left_descr, right_descr;
                auto idx = pivot - hatoms.begin();
                for (int k = 0; k < (int)hatoms.size(); k++)
                {
                    auto& nextL = chainAt(hatoms, idx + k);
                    auto& nextR = chainAt(hatoms, idx - k);
                    auto number = to<string>(k + 1);
                    left_descr += nextL.first + number;
                    right_descr += nextR.first + number;
                }
                buffer << (left_descr < right_descr ? left_descr : right_descr);
            }
            outputPieceCycle(buffer.str(), fragment);
        }
        //make a map of intersections
        intermap.resize(cycles.size()*cycles.size());
        intermap.clear();
        for (size_t i = 0; i < cycles.size(); i++)
        for (size_t j = i + 1; j < cycles.size(); j++)
        {
            auto& c1 = cycles[i];
            auto& c2 = cycles[j];
            bool intersection = c1.intersects(c2);
            intermap[i*cycles.size() + j] = intersection;
            intermap[j*cycles.size() + i] = intersection;
        }/*
        for (size_t i = 0; i < cycles.size(); i++)
        {
            for (size_t j = 0; j < cycles.size(); j++)
            {
                cout << intermap[i*cycles.size() + j];
            }
            cout << endline;
        }*/
        //
        vector<int> ccv; //chain - indices of cycles
        vector<bool> used(cycles.size());
        do
        {
            ccv.clear();
            for (size_t i = 0; i < cycles.size();)
            {
                if (used[i])
                {
                    i++;
                    continue;
                }
                if (ccv.empty())
                {
                    used[i] = true;
                    ccv.push_back(i);
                    i++; // первый непроверенный, нет нужды начинать с 0
                    continue;
                }
                //then must interesect some other cycle in this chain
                bool found = false;
                for (auto j : ccv)
                {
                    if (intermap[i*cycles.size() + j])
                    {
                        found = true;
                        break;
                    }
                }
                if (found)
                {
                    used[i] = true;
                    ccv.push_back(i);
                    i = 0; //нужно перепроверить циклы
                    continue;
                }
                i++;
            }
            if (ccv.size() > 1) //элементарные циклы уже учтены
            {
                encodePolyCycles(ccv);
            }
        } while (!ccv.empty());
    }

    // return all descriptors at dcV vertex
    vector<int> mapDCs(size_t dcV)
    {
        vector<int> dc_nums;
        for(auto p : dcs){
            if(p.first == dcV)
                dc_nums.push_back(p.second);
        }
        return dc_nums;
    }

    void replacement(ostream& out)
    {
        //cout << "REPLACEMENTS!" << endline;
        for (auto& r : repls)
        {
            vector<vector<size_t>> mappings;
            auto& g = graph;

            vf2_subgraph_iso(r.piece, g, CollectAsVectors(g, mappings), vertex_order_by_mult(r.piece),
                vertices_equivalent([&](ChemGraph::vertex_descriptor a, ChemGraph::vertex_descriptor b){
                    if(!r.piece[a].code.matches(g[b].code))
                        return false;
                    if(a == r.a1 || a == r.a2){
                        if(g[b].inAromaCycle) // none of replacemnt dc are in aroma cycle (e.g. DC 41 is CH3)
                            return false;
                        return find_if(dcs.begin(), dcs.end(), [&](pair<vd,size_t> dcp){
                            return dcp.first == b;
                        }) != dcs.end();
                    }
                    else
                        return true;
                }).edges_equivalent(
                [&r, &g](ChemGraph::edge_descriptor a, ChemGraph::edge_descriptor b){
                    return r.piece[a].type == g[b].type;
                })
            );
            vector<pair<pair<int, int>, pair<int, int>>> used_pairs;
            for (auto& m : mappings)
            {
                int fV = m[r.a1];
                int sV = m[r.a2];
                // check if this pair of vertex-DC pairs was used before
                auto fdc = mapDCs(fV);
                if (fdc.size() == 0)
                    continue;
                auto sdc = mapDCs(sV);
                if (sdc.size() == 0)
                    continue;
                for(auto f : fdc)
                for(auto s : sdc)
                {
                    auto firstDC = f;
                    auto secondDC = s;
                    auto firstV = fV;
                    auto secondV = sV;
                    // the usual rule of smaller DC first
                    if(firstDC > secondDC)
                    {
                        swap(firstDC, secondDC);
                        swap(firstV, secondV);
                    }
                    auto check_val = make_pair(make_pair(firstV, firstDC), make_pair(secondV,secondDC));
                    if (find(used_pairs.begin(), used_pairs.end(),check_val) != used_pairs.end())
                            continue;
                    used_pairs.emplace_back(make_pair(firstV, firstDC), make_pair(secondV,secondDC));

                    stringstream buffer;
                    vector<int> fragment;
                    for(auto& p : m)
                        fragment.push_back(p);
                    addDescriptorAtoms(fragment, fV);
                    addDescriptorAtoms(fragment, sV);

                    buffer << setfill('0') << setw(2) << firstDC
                        << setfill('0') << setw(2) << r.dc
                        << setfill('0') << setw(2) << secondDC
                        << r.coupling;
                    outputPiece(buffer.str(), fragment);
                }
            }
        }
    }

private:
    std::vector<LevelOne> order1;        // patterns for first-order DCs
    std::vector<LevelTwo> order2;        // patterns for second-order DCs
    std::vector<Replacement> repls; // patterns for replacement decsriptors (not DCs)
    bool long41;                                        // if true - DC #41 adds +1 to the length of chain
    FCSPFMT format;                                     // controls output format
    ChemGraph graph;                                // mol graph
    //location of DCs in 'graph' and their numeric value
    vector<pair<vd, int>> dcs;            // sorted by vertex array of vertex->dc mappings
    std::unordered_map<vd, vector<int>> dcsAtoms;  // extra atoms that belong to each DC
    std::unordered_map<vd, int> reserved_dcs; // atoms reserved by specific monolithic DC
    // unassembled output chunks, assembly depends on format variable
    vector<string> outPiecesCycle; // cycle descriptors go first on assembly
    vector<string> outPieces;
    //sorted arrays of edges - basic cycles
    //vector<vector<pair<int, int>>> cycles;
    //same basic cycles represented as chains of vertices
    //vector<vector<int>> chains;
    vector<Cycle> cycles;
    //map of intersection between cycles
    vector<bool> intermap;
};

FCSP::FCSP(FCSPOptions opts) :
    pimpl(new FCSP::Impl(std::move(opts))){}

void FCSP::load(std::istream& inp)
{
    pimpl->load(inp);
}

void FCSP::dumpGraph(std::ostream& dot)
{
    pimpl->dumpGraph(dot);
}

void FCSP::process(std::ostream& out, string filename)
{
    pimpl->process(out, filename);
}

FCSP::~FCSP(){}


#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <boost/foreach.hpp>

using namespace std;

class HDB3Parser
{
public:
	//public entry point for encode
	vector<string> Encode(vector<string> data)
	{
		for each (auto item in data)
			this->_convertedData.push_back(EncodeToHDB3(item));

		return this->_convertedData;
	}

	//public entry point for decode
	vector<string> Decode(string readFilename, string writeFilename)
	{
		ReadWrite(readFilename, true);

		for each (auto item in this->_data)
			this->_convertedData.push_back(DecodeFromHDB3(item));

		ReadWrite(writeFilename, false);

		return this->_convertedData;
	}

private:
	vector<string> _data;
	vector<string> _convertedData;

	//reads or writes to a file
	void ReadWrite(string filename, bool read)
	{
		fstream stream;
		string lineIn;
		string temp;

		stream.open(filename);

		if (stream.good())
		{
			if (read)
			{
				cout << "Reading contents of " << filename << "..." << endl;
				while (stream.good())
				{
					getline(stream, lineIn);

					this->_data.push_back(lineIn);
				}
			}
			else
			{
				cout << "Saving data to " << filename << "..." << endl;

				for each (auto str in this->_convertedData)
				{
					if (str != "")
						stream << str << endl;
				}
			}
			stream.close();
		}
		else
			cout << "ERROR: Could not open file: " << filename << endl;
	}

	//encodes given string to HDB3
	string EncodeToHDB3(string item)
	{
		int numZeroes = 0;
		int numPulseCharacters = 0;
		bool prevPulseWasPositive = false;
		bool isAfterFirstViolation = false;
		bool tempPulseBool;
		item = ConvertToAMI(item, false);

		for (int i = 0; i < item.length(); i++)
		{
			if (item[i] == '+')
			{
				prevPulseWasPositive = true;
				numPulseCharacters++;
			}
			else if (item[i] == '-')
			{
				prevPulseWasPositive = false;
				numPulseCharacters++;
			}

			if (item[i] == '0' && item.length() - i >= 4 && item.substr(i, 4) == "0000")
			{
				if (!isAfterFirstViolation)
					isAfterFirstViolation = true;

				if (isOdd(numPulseCharacters))
				{
					if (prevPulseWasPositive)
					{
						item.replace(i, 4, "-00-");
						prevPulseWasPositive = false;
					}
					else
					{
						item.replace(i, 4, "+00+");
						prevPulseWasPositive = true;
					}
					numPulseCharacters = 1;
				}
				else
				{
					if (prevPulseWasPositive)
						item.replace(i, 4, "000+");
					else
						item.replace(i, 4, "000-");

					numPulseCharacters = 1;
				}

				i += 3;
				tempPulseBool = prevPulseWasPositive;
				item = CheckForAMIViolations(item, i, prevPulseWasPositive);

				prevPulseWasPositive = tempPulseBool;
			}
		}
		return item;
	}

	//converts given string to AMI
	string ConvertToAMI(string binaryStr, bool prevPulseWasPositive)
	{
		for (int i = 0; i < binaryStr.length(); i++)
		{
			if (binaryStr[i] == '1' && !prevPulseWasPositive)
			{
				binaryStr[i] = '+';
				prevPulseWasPositive = true;
			}
			else if (binaryStr[i] == '1' && prevPulseWasPositive)
			{
				binaryStr[i] = '-';
				prevPulseWasPositive = false;
			}
		}
		return binaryStr;
	}

	//checks for AMI violations in given string and corrects them
	string CheckForAMIViolations(string item, int currentIndex, bool prevPulseWasPositive)
	{
		int first = currentIndex + 1;
		bool tempPulseBool = prevPulseWasPositive;
		int numAmiViolations = 0;

		for (first; first < item.length(); first++)
		{
			if (prevPulseWasPositive && item[first] == '+')
			{
				item[first] = '-';
				prevPulseWasPositive = false;
				numAmiViolations++;
			}
			else if (!prevPulseWasPositive && item[first] == '-')
			{
				item[first] = '+';
				prevPulseWasPositive = true;
				numAmiViolations++;
			}
			else if (prevPulseWasPositive && item[first] == '-')
				prevPulseWasPositive = false;
			else if (!prevPulseWasPositive && item[first] == '+')
				prevPulseWasPositive = true;
		}
		return item;
	}

	//determines if given number is odd
	bool isOdd(int num)
	{
		if (num % 2 == 1)
			return true;
		return false;
	}

	//decodes given string from HDB3 to binary string
	string DecodeFromHDB3(string item)
	{
		bool prevPulseWasPositive = false;

		for (int i = 0; i < item.length(); i++)
		{
			if (i == 0 && item.length() >= 4 && item.substr(i, 4) == "000+")
			{
				item[i + 3] = '1';
				prevPulseWasPositive = true;
				continue;
			}
			if (item[i] == '+')
			{
				if (item.substr(i, 4) != "+00+")
					item[i] = '1';
				else if (item.length() - i >= 4 && (item[i] == '+' && item.substr(i, 4) == "+00+"))
				{
					item.replace(i, 4, "0000");
					i += 3;
				}
				prevPulseWasPositive = true;
			}
			else if (item[i] == '-')
			{
				if (item.substr(i, 4) != "-00-")
					item[i] = '1';
				else if (item.length() - i >= 4 && (item[i] == '-' && item.substr(i, 4) == "-00-"))
				{
					item.replace(i, 4, "0000");
					i += 3;
				}
				prevPulseWasPositive = false;
			}
			else if (item[i] == '0' && item.length() - i >= 4)
			{
				if (prevPulseWasPositive && item.substr(i, 4) == "000+")
				{
					item.replace(i, 4, "0000");
					i += 3;
					prevPulseWasPositive = true;
				}
				else if (!prevPulseWasPositive && item.substr(i, 4) == "000-")
				{
					item.replace(i, 4, "0000");
					i += 3;
					prevPulseWasPositive = false;
				}
				else if (!prevPulseWasPositive && item.substr(i, 4) == "000+")
				{
					item.replace(i, 4, "0001");
					i += 3;
					prevPulseWasPositive = true;
				}
				else if (prevPulseWasPositive && item.substr(i, 4) == "000-")
				{
					item.replace(i, 4, "0001");
					i += 3;
					prevPulseWasPositive = false;
				}
			}
		}
		return item;
	}
};


// This file is part of sibilla : inference in epidemics with Belief Propagation
// Author: Alfredo Braunstein

#include <pybind11/pybind11.h>
#include <pybind11/stl_bind.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <pybind11/pytypes.h>
#include <pybind11/iostream.h>
#include <string>
#include <sstream>
#include <numeric>
#include <boost/lexical_cast.hpp>
#include <iterator>
#include <exception>
#include "bp.h"
#include "mf.h"

typedef NodeType<BPMes> Node;
typedef NeighType<BPMes> Neigh;

PYBIND11_MAKE_OPAQUE(std::valarray<real_t>);
PYBIND11_MAKE_OPAQUE(std::vector<real_t>);
PYBIND11_MAKE_OPAQUE(std::vector<int>);
PYBIND11_MAKE_OPAQUE(std::vector<Node>);

namespace py = pybind11;
using namespace std;
using boost::lexical_cast;

vector<real_t> make_vector(py::list & l)
{
    vector<real_t> v(l.size());
    int i = 0;
    for (py::handle o : l) {
        v[i++] = py::cast<real_t>(o);
    }
    return v;
}

map<int, vector<times_t> >
get_times(BPGraph const & f) {
        map<int, vector<times_t> > times;
        for (int i = 0; i < int(f.nodes.size()); ++i) {
            times[i] = f.nodes[i].times;
            times[i].pop_back();
        }
        return times;
}

vector<tuple<real_t, real_t, real_t>>
get_marginal(Node const & n)
{
        vector<real_t> rbt(n.bt.size());
        vector<real_t> lbg(n.bg.size());
        int const T = n.bt.size() - 1;
        lbg[0] = n.bg[0];
        rbt[T] = n.bt[T];
        for (int t = 1; t <= T; ++t) {
            lbg[t] = lbg[t-1] + n.bg[t];
            rbt[T - t] = rbt[T - t + 1] + n.bt[T - t];
        }
        auto marg = vector<tuple<real_t, real_t, real_t>>(T-1);
        for (int t = 1; t < T; ++t)
            marg[t-1] = make_tuple(rbt[t], 1-rbt[t]-lbg[t-1], lbg[t-1]);
        return marg;
}

tuple<real_t, real_t, real_t> get_marginal_index(Node const & n, int t)
{
        if (t < 0 || t >= int(n.bt.size()) - 2)
            throw py::key_error("Time out of range");
        return get_marginal(n)[t];
}

void check_index(BPGraph const & G, int i)
{
        if (i < 0 || i >= int(G.nodes.size()))
                throw invalid_argument("unexistent index");

}

template<int i>
real_t mygetter(Proba & p)
{
    return p.theta[i];
}

template<int i>
void mysetter(Proba & p, real_t x)
{
    p.theta[i] = x;
}

template<class TMes>
void init_factorgraph(py::module & m)
{
    py::class_<FactorGraph<TMes>>(m, FactorGraph<TMes>::name(), "SIB class representing the graphical model of the epidemics")
        .def(py::init<Params const &,
                vector<tuple<int,int,times_t,real_t>>,
                vector<tuple<int,int,times_t>>,
                vector<tuple<int,shared_ptr<Proba>,shared_ptr<Proba>,shared_ptr<Proba>,shared_ptr<Proba>>>
                >(),
                py::arg("params") = Params(shared_ptr<Proba>(new Uniform(1.0)), shared_ptr<Proba>(new Exponential(0.5)), 0.1, 0.45, 0.0, 0.0, 0.0, 0.0),
                py::arg("contacts") = vector<tuple<int,int,times_t,real_t>>(),
                py::arg("observations") = vector<tuple<int,int,times_t>>(),
                py::arg("individuals") = vector<tuple<int,shared_ptr<Proba>,shared_ptr<Proba>,shared_ptr<Proba>,shared_ptr<Proba>>>())
        .def("update", &FactorGraph<TMes>::iteration,
                py::arg("damping") = 0.0,
                py::arg("learn") = false,
                "perform one iteration")
        .def("loglikelihood", &FactorGraph<TMes>::loglikelihood, "compute the bethe log-likelihood")
        .def("__repr__", &lexical_cast<string, FactorGraph<TMes>>)
        .def("append_contact", (void (FactorGraph<TMes>::*)(int,int,times_t,real_t,real_t)) &FactorGraph<TMes>::append_contact,
                py::arg("i"),
                py::arg("j"),
                py::arg("t"),
                py::arg("lambdaij"),
                py::arg("lambdaji") = real_t(FactorGraph<TMes>::DO_NOT_OVERWRITE),
                "appends a new contact from i to j at time t with transmission probabilities lambdaij, lambdaji")
        .def("reset_observations", &FactorGraph<TMes>::reset_observations,
                py::arg("obs"),
                "resets all observations")
        .def("append_observation", &FactorGraph<TMes>::append_observation,
                py::arg("i"),
                py::arg("s"),
                py::arg("t"),
                "appends a new observation with state s to node i at time t")
        .def("show", &FactorGraph<TMes>::show_graph)
        .def("drop_contacts", &FactorGraph<TMes>::drop_contacts, "drop contacts at time t (first time)")
        .def("drop_time", &drop_time<BPMes>, "drop time t (first time)")
        .def("showmsg", [](FactorGraph<TMes> & f){f.show_msg(std::cout);}, "show messages for debugging")
        .def_readonly("nodes", &FactorGraph<TMes>::nodes, "all nodes in this FactorGraph<TMes>")
        .def_readonly("params", &FactorGraph<TMes>::params, "parameters");

    py::class_<NodeType<TMes>>(m, NodeType<TMes>::name(), "SIB class representing an individual")
        .def("marginal", &get_marginal, "compute marginal probabilities (pS,pI,pR) corresponding to times n.times[1:]")
        .def("marginal_index", &get_marginal_index, "marginal at a given time (excluding time -1)")
        .def_readwrite("ht", &NodeType<TMes>::ht, "external prior on ti")
        .def_readwrite("hg", &NodeType<TMes>::hg, "external prior on gi")
        .def_readonly("bt", &NodeType<TMes>::bt, "belief on ti")
        .def_readonly("bg", &NodeType<TMes>::bg, "belief on gi")
        .def_readonly("err", &NodeType<TMes>::err_, "error on update")
        .def_readonly("df_i", &NodeType<TMes>::df_i, "gradient on prior_i params")
        .def_readonly("df_r", &NodeType<TMes>::df_r, "gradient on prior_r params")
        .def_readonly("times", &NodeType<TMes>::times, "event times of this node")
        .def_readonly("index", &NodeType<TMes>::index, "node index (deprecated, do not use)")
        .def_readonly("prob_i", &NodeType<TMes>::prob_i, "probability of infection as function of t-ti")
        .def_readonly("prob_r", &NodeType<TMes>::prob_r, "cumulative probability of recovery P(tr>t)")
        .def_readonly("prob_i0", &NodeType<TMes>::prob_i0, "probability of infection as function of t-ti for ti=0")
        .def_readonly("prob_r0", &NodeType<TMes>::prob_r0, "cumulative probability of recovery P(tr>t) for ti=0");

}

void init_realparams(py::module & m)
{
    py::class_<RealParams>(m, "RealParams", py::buffer_protocol())
        .def(py::init([](py::buffer const b) {
                py::buffer_info info = b.request();
                if (info.format != py::format_descriptor<real_t>::format() || info.ndim != 1)
                throw std::runtime_error("Incompatible buffer format!");

                auto v = new RealParams(info.shape[0]);
                memcpy(&(*v)[0], info.ptr, sizeof(real_t) * (size_t) (v->size()));
                return v;
                }))
        .def(py::init([](vector<real_t> const & p)->RealParams {return RealParams(&p[0], p.size());}))
        .def(py::init([](py::list & l)->RealParams {auto v = make_vector(l); return RealParams(&v[0], v.size());}))
        .def_buffer([](RealParams &p) -> py::buffer_info {
            return py::buffer_info(
                &p[0],                               /* Pointer to buffer */
                sizeof(real_t),                          /* Size of one scalar */
                py::format_descriptor<real_t>::format(), /* Python struct-style format descriptor */
                1,                                      /* Number of dimensions */
                { p.size() },                 /* Buffer dimensions */
                { sizeof(real_t) }             /* Strides (in bytes) for each index */
                );
        })
        .def("__add__", [](RealParams & p, RealParams & q)->RealParams { return p + q; })
        .def("__getitem__", [](const RealParams &p, ssize_t i) {
                if (i > int(p.size()))
                    throw py::index_error();
                return p[i];
                })
        .def("__setitem__", [](RealParams &p, ssize_t i, real_t v) {
                if (i > int(p.size()))
                    throw py::index_error();
                p[i] = v;
                })
        .def("__repr__", [](RealParams &p) {
                    string s = "RealParams([";
                    for (size_t i = 0; i < p.size(); ++i)
                        s += (i ? ",":"") + lexical_cast<string>(p[i]);
                    s+="])";
                    return s;
                });
}

void init_proba(py::module & m)
{
    py::class_<Proba, shared_ptr<Proba>>(m, "Proba")
        .def("__call__", [](Proba const & p, real_t d) { return p(d); } )
        .def("grad", [](Proba const & p, real_t d) { RealParams dtheta(0.0, p.theta.size()); p.grad(dtheta, d); return dtheta;} )
        .def("__repr__", &lexical_cast<string, Proba>)
        .def_readwrite("theta", &Proba::theta);

    py::class_<Uniform, Proba, shared_ptr<Uniform>>(m, "Uniform")
        .def(py::init<real_t>(), py::arg("p") = 1.0)
        .def_property("p", &mygetter<0>, &mysetter<0>);

    py::class_<Exponential, Proba, shared_ptr<Exponential>>(m, "Exponential")
        .def(py::init<real_t>(), py::arg("mu") = 0.1)
        .def_property("mu", &mygetter<0>, &mysetter<0>);

    py::class_<Gamma, Proba, shared_ptr<Gamma>>(m, "Gamma")
        .def(py::init<real_t, real_t>(), py::arg("k") = 1.0, py::arg("mu") = 0.1)
        .def_property("k", &mygetter<0>, &mysetter<0>)
        .def_property("mu", &mygetter<1>, &mysetter<1>);

    py::class_<UnnormalizedGammaPDF, Proba, shared_ptr<UnnormalizedGammaPDF>>(m, "UnnormalizedGammaPDF")
        .def(py::init<real_t, real_t>(), py::arg("k") = 1.0, py::arg("mu") = 0.1)
        .def_property("k", &mygetter<0>, &mysetter<0>)
        .def_property("mu", &mygetter<1>, &mysetter<1>);
    py::class_<PiecewiseLinear, Proba, shared_ptr<PiecewiseLinear>>(m, "PiecewiseLinear")
        .def(py::init<RealParams const &, real_t>(), py::arg("theta"), py::arg("step") = 1.0)
        .def(py::init<Proba const &, int, real_t>(), py::arg("prob"), py::arg("num"), py::arg("step") = 1.0);

    py::class_<Cached, Proba, shared_ptr<Cached>>(m, "Cached")
        .def(py::init<std::shared_ptr<Proba> const &, int>(), py::arg("prob"), py::arg("T"))
        .def_property("theta", &Cached::get_theta, &Cached::set_theta);

    py::class_<Scaled, Proba, shared_ptr<Scaled>>(m, "Scaled")
        .def(py::init<std::shared_ptr<Proba> const &, real_t>(), py::arg("prob"), py::arg("scale") = 1.0);

    py::class_<PDF, Proba, shared_ptr<PDF>>(m, "PDF")
        .def(py::init<std::shared_ptr<Proba> const &>());

}

void init_params(py::module & m)
{
    py::class_<Params>(m, "Params")
        .def(py::init<shared_ptr<Proba> const &, shared_ptr<Proba> const &, real_t, real_t, real_t, real_t, real_t, real_t>(),
                "SIB Params class. prob_i and prob_r parameters are defaults.",
                py::arg("prob_i") = *new Uniform(1.0),
                py::arg("prob_r") = *new Exponential(0.1),
                py::arg("pseed") = 0.01,
                py::arg("psus") = 0.5,
                py::arg("fp_rate") = 0.0,
                py::arg("fn_rate") = 0.0,
                py::arg("pautoinf") = 0.0,
                py::arg("learn_rate") = 0.0)

        .def_readwrite("prob_r", &Params::prob_r)
        .def_readwrite("prob_i", &Params::prob_i)
        .def_readwrite("pseed", &Params::pseed)
        .def_readwrite("psus", &Params::psus)
        .def_readwrite("fp_rate", &Params::fp_rate)
        .def_readwrite("fn_rate", &Params::fn_rate)
        .def_readwrite("pautoinf", &Params::pautoinf)
        .def_readwrite("learn_rate", &Params::learn_rate)
        .def("__repr__", &lexical_cast<string, Params>);
}

PYBIND11_MODULE(_sib, m) {
    init_realparams(m);
    init_proba(m);
    // py::add_ostream_redirect(m, "ostream_redirect");
    py::bind_vector<std::vector<int>>(m, "VectorInt");
    py::bind_vector<std::vector<real_t>>(m, "VectorReal");
    py::bind_vector<std::vector<Node>>(m, "VectorNode");

    init_params(m);
    init_factorgraph<BPMes>(m);
    init_factorgraph<MFMes>(m);

    m.def("set_num_threads", &omp_set_num_threads, "sets the maximum number of simultaneous cpu threads");
    m.def("version", [](){return VERSION;}, "compiled version of sib");
}


// **************************************************************************************************
//
// The MIT License (MIT)
//
// Copyright (c) 2017 Pierre Lebreton
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// 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.
//
// **************************************************************************************************

#include "GBVS360.h"

#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <limits>
#include <set>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>

#include <stdlib.h>
#include <time.h>

#include <HMDSim.h>
#include <VPD.h>

#include "Projection.h"
#include "Options.h"
#include "CSVReader.h"

#define STUDY_FIX_PREDICTION 1

GBVS360::GBVS360(boost::shared_ptr<Projection> projection) {
	m_Projection = projection;

	blurfrac		= 0.02f;
	salmapmaxsize 	= 60;
	featureScaling	= 1.0f;
	hmdMode = false;
}

void GBVS360::compute(const cv::Mat& input, cv::Mat &output, bool normalize) {

	assert(input.channels() == 3 && input.type() == CV_8UC3);

	nbThreads = std::max<int>(2, static_cast<int>(Option::threads / 3.f));

	// reset the framework....
	m_ProjectedFrames.clear();
	featureDone.clear();
	m_GBVSWorkers.clear();
	clear(); 					// reset GBVS features and keep init done.

	m_InputImage = input;

	// Let's start the analysis
	m_EquirectangularFrameSize = input.size();

	std::cout << "[I] Init graph framework -- scheduled" << std::endl;
	boost::thread_group group;
	group.create_thread(boost::bind(&GBVS360::initGBVS, this, boost::ref(input)));

	std::cout << "[I] Getting rectilinear frames" << std::endl;
	getRectilinearFrames(input);

	// get feature maps for each equililnear frame
	std::cout << "[I] Getting features per frame" << std::endl;
	getRectilinearFeatures();

	// back project feature maps to equirectangular coordinate
	std::cout << "[I] Getting back-projected features" << std::endl;
	getEquirectangularFeatures();

	std::cout << "[I] Waiting graph initialization to finish" << std::endl;
	group.join_all();

	std::cout << "[I] Applying GBVS Activation, Normalization & Pooling" << std::endl;

	// GBVS regular flow
	gbvsActivation(input, output, normalize);

	if(equatorialPrior) {
		applyEquatorialPrior(output, input);

		if(normalize) {
			double mn, mx;
			cv::minMaxLoc(output, &mn, &mx);
			output = (output - mn) / (mx - mn);
		}

	}

	if (Option::exportRawFeatures) {
		for (std::list<FeatureMap>::const_iterator it = allmaps.begin(); it != allmaps.end(); ++it) {
			FILE *f = fopen((Option::prefix + "_" + boost::lexical_cast<std::string>(it->channel) + "_" + boost::lexical_cast<std::string>(it->level) + "_" + boost::lexical_cast<std::string>(it->type) + ".csv").c_str(), "w");
			if (f == NULL) {
				continue;
			}

			for (int i = 0; i < it->map.rows; ++i) {
				for (int j = 0; j < it->map.cols-1; ++j) {
					fprintf(f, "%20.20lf, ", it->map.at<double>(i, j));
				}
				fprintf(f, "%20.20lf\n", it->map.at<double>(i, it->map.cols - 1));
			}

			fclose(f);
		}
	}

}

struct FixationOptionComparison {
	bool operator()(const FixationOption &a, const FixationOption &b) const {
		if(std::abs(a.value - b.value) < 0.000000001f) {
			if (std::abs(a.probability - b.probability) < 0.000000001f) {
				if(a.x == b.x) {
					return a.y < b.y;
				} else {
					return a.x < b.x;
				}
			} else {
				return (a.probability < b.probability);
			}
		}
		return a.value < b.value;
	}
};

struct FixationOptionComparison2 {
	bool operator()(const FixationOption &a, const FixationOption &b) const {
		if (std::abs(a.probability - b.probability) < 0.000000001f) {
			if(std::abs(a.value - b.value) < 0.000000001f) {
				if(a.x == b.x) {
					return a.y < b.y;
				} else {
					return a.x < b.x;
				}
			} else {
				return a.value < b.value;
			}
		}
		return (a.probability < b.probability);

	}
};

typedef std::set<FixationOption, FixationOptionComparison2> TFixationsSet;

void GBVS360::scanPath(const cv::Mat& imgBGR, cv::Mat &out, bool inputSaliencyMap) {

	// -------------------------------------------------------------------------------------------
	// First, we need the saliency map

	// if the input provided is not already a saliency map, then compute it using the GBVS360 framework
	if(!inputSaliencyMap) {
		cv::Mat saliency;
		compute(imgBGR, saliency);
	}

	// Check if the GBVS360 framework was initialized (it is the case if it computed the saliency map, it was provided as input the initialization is required)
	if(!initDone)
		initGBVS(imgBGR);

	CSVReader<float> csv;
	if(!Option::scanPath.empty()) {
		if(!csv.open(Option::scanPath)) {
			std::cout << "[I] cannot open csv file: " << Option::scanPath << std::endl;
		}
	}

	std::vector<FixationOption> groundTruthFixations;
	if(csv.getCols() > 0) {
		int obsIdx = -1;
		for(size_t i = 0 ; i < csv[0].size() ; ++i) {
			if(csv[0][i] == 1) {
				++obsIdx;
			}

			if(obsIdx == Option::scanPathUserIdx) {
				FixationOption f;
				f.x = static_cast<int>(csv[2][i]);
				f.y = static_cast<int>(csv[3][i]);
				f.value = csv[0][i];
				f.probability = csv[1][i];

				groundTruthFixations.push_back(f);
			}
		}
	}

	// Check the input: if a saliency map was provided, it can be in BGR or gayscale.
	cv::Mat saliency;
	if(inputSaliencyMap) {
		saliency = imgBGR.clone();
		if(imgBGR.channels() == 3 && imgBGR.type() == CV_8UC3) {
			cv::cvtColor(saliency, saliency, cv::COLOR_BGR2GRAY);
			saliency.convertTo(saliency, CV_64FC1);
			saliency /= 255;
		} else {
			saliency.convertTo(saliency, CV_64FC1);
		}

		cv::resize(saliency, master_map, cv::Size(salmapmaxsize_v[1], salmapmaxsize_v[0]), 0, 0, cv::INTER_AREA);
	}

#ifdef STUDY_FIX_PREDICTION

	cv::resize(saliency, saliency, saliency.size()/5, 0, 0, cv::INTER_AREA);

#endif

	// -------------------------------------------------------------------------------------------
	// prepare the state transition matrix

	const cv::Mat &lx = grframe.lx;

	// form a multiresolution pyramid of feature maps according to multilevels
	std::vector<cv::Mat> apyr;
	std::vector< std::pair<int, int> > dims;
	formMapPyramid(master_map, grframe.multilevels, apyr, dims);

	// assign a linear index to each node
	std::vector<double> AL;
	arrangeLinear(apyr , dims, AL);

	// create the state transition matrix between nodes
	cv::Mat mm(lx.rows, lx.rows, CV_64FC1, cv::Scalar(0.f));

	// assign edge weights based on distances between nodes and algtype

	for(int c = 0 ; c < static_cast<int>(AL.size()) ; ++c) {
		for(int r = 0 ; r < static_cast<int>(AL.size()) ; ++r) {
			mm.at<double>(r,c) =  std::abs( AL[r] - AL[c] );
		}
	}

	// make it a markov matrix (so each column sums to 1)
	columnNormalize(mm);

	// inverse the weight, as we want to convert normalized distance to probability of transition
	// two elements with a distance of 0 should have a high chance to be connected
	mm = 1 - mm;

	// -------------------------------------------------------------------------------------------
	// Use the state transition matrix to predict scan path

	srand(time(NULL));

#ifdef STUDY_FIX_PREDICTION

	saliency.convertTo(saliency, CV_32FC3);

#endif

	out = cv::Mat(Option::numberFixations * Option::experimentRepppetition, 4, CV_32FC1, cv::Scalar(0.f));
	for(int i = 0 ; i < Option::experimentRepppetition ; ++i) {
		int initPosition = static_cast<int>((i * static_cast<float>(master_map.cols) / static_cast<float>(Option::experimentRepppetition)) * master_map.rows + master_map.rows / 2);
		cv::Mat result = runScanPath(saliency, imgBGR, groundTruthFixations, lx, mm, initPosition);

		for(int ii = 0 ; ii < result.rows ; ++ii) {
			for(int jj = 0 ; jj < result.cols ; ++jj) {
				out.at<float>(i*Option::numberFixations + ii, jj) = result.at<float>(ii,jj);
			}
		}
	}

}

void GBVS360::getTransitionMatrix(const cv::Mat &lx, const cv::Mat &mm, cv::Mat &distanceMatrix, int position, float scaling) const {
	double sig = sigma_frac_act * static_cast<double>(master_map.cols + master_map.rows)/2.f;

	for(int l = 0 ; l < lx.rows ; ++l) {
		distanceMatrix.at<float>(l % master_map.rows, l / master_map.rows) = static_cast<float>(mm.at<double>(l, position)); //
	}

	double mx, mn;
	cv::minMaxLoc(distanceMatrix, &mn, &mx);

	distanceMatrix = (distanceMatrix - mn) / (mx - mn);

	for(int l = 0 ; l < lx.rows ; ++l) {

		float distX = static_cast<float>(std::min( std::abs((l / master_map.rows)  -  (position / master_map.rows)) , std::abs( std::abs((l / master_map.rows)  -  (position / master_map.rows)) - master_map.cols ) ));
		float distY = static_cast<float>(std::min( std::abs((l % master_map.rows)  -  (position % master_map.rows)) , std::abs( std::abs((l % master_map.rows)  -  (position % master_map.rows)) - master_map.rows ) ));
		float dist = (distX*distX + distY*distY);

		distanceMatrix.at<float>(l % master_map.rows, l / master_map.rows) = static_cast<float>(std::exp( -1 *( dist / scaling ) / (2 * sig*sig)) * distanceMatrix.at<float>(l % master_map.rows, l / master_map.rows)); //
	}

	distanceMatrix = distanceMatrix * (mx - mn) + mn;
}

#ifdef STUDY_FIX_PREDICTION
cv::Mat GBVS360::runScanPath(const cv::Mat& saliency, const cv::Mat& imgBGR, const std::vector<FixationOption>& groundTruthFixations, const cv::Mat &lx, const cv::Mat &mm, int initPosition) {
#else
cv::Mat GBVS360::runScanPath(const cv::Mat& , const cv::Mat& imgBGR, const std::vector<FixationOption>& , const cv::Mat &lx, const cv::Mat &mm, int initPosition) {
#endif

	cv::Mat out = cv::Mat(Option::numberFixations, 4, CV_32FC1, cv::Scalar(0.f));

	// start from the center
	int position = initPosition;

#ifdef STUDY_FIX_PREDICTION

	float scaling = static_cast<float>(saliency.cols / master_map.cols);

	cv::Mat saliency2 = saliency.clone();

#endif

	// if start from a non salient area, find one.
	if(master_map.at<double>(position % master_map.rows, position / master_map.rows) < 0.7) {
		float localMax = 0;
		int pos_x = position % master_map.cols;

		for(int ii = master_map.rows/3 ; ii < master_map.rows - master_map.rows/3 ; ++ii) {
			for(int jj = std::max(0, pos_x-master_map.cols/6) ; jj < std::min(master_map.cols/6, pos_x+master_map.cols/6) ; ++jj) {

				if(master_map.at<double>(ii,jj) > localMax) {
					position = jj * master_map.rows + ii;
					localMax = static_cast<float>(master_map.at<double>(ii,jj));
				}
			}

			if((pos_x-master_map.cols/6) < 0) {
				for(int jj = master_map.cols + (pos_x-master_map.cols/6) ; jj < master_map.cols ; ++jj) {

					if(master_map.at<double>(ii,jj) > localMax) {
						position = jj * master_map.rows + ii;
						localMax = static_cast<float>(master_map.at<double>(ii,jj));
					}
				}
			}

			if((pos_x+master_map.cols/6) > master_map.cols) {
				for(int jj = 0 ; jj < (pos_x+master_map.cols/6) - master_map.cols ; ++jj) {

					if(master_map.at<double>(ii,jj) > localMax) {
						position = jj * master_map.rows + ii;
						localMax = static_cast<float>(master_map.at<double>(ii,jj));
					}
				}
			}

		}
	}

	// find local maxiam
	{
		int lx = position / master_map.rows;
		int ly = position % master_map.rows;

		int plx = -1;
		int ply = -1;
		while(plx != lx && ply != ly) {
			plx = lx; ply = ly;
			if((lx + 1 < master_map.cols) && master_map.at<double>(ly, lx+1) > master_map.at<double>(ly, lx)) {
				++lx;
			}
			if((lx - 1 > 0) && master_map.at<double>(ly, lx-1) > master_map.at<double>(ly, lx)) {
				--lx;
			}
			if((ly + 1 < master_map.rows) && master_map.at<double>(ly+1, lx) > master_map.at<double>(ly, lx)) {
				++ly;
			}
			if((ly - 1 > 0) && master_map.at<double>(ly-1, lx) > master_map.at<double>(ly, lx)) {
				--ly;
			}
		}

		position = lx * master_map.rows + ly;
	}

	double smx, smn;
	cv::minMaxLoc(master_map, &smn, &smx);

	std::list<cv::Point2i> fixationHistory;
	fixationHistory.push_back(cv::Point2i(position / master_map.rows, position % master_map.rows));

	cv::Mat distanceMatrix(master_map.size(), CV_32FC1, cv::Scalar(0));
	for(int i = 0 ; i < Option::numberFixations ; ++i) {
		// lookup the transition probability for all other points

		getTransitionMatrix(lx, mm, distanceMatrix, position, 1);

		double mx, mn;
		cv::minMaxLoc(distanceMatrix, &mn, &mx);

		double localThreshold = mn + (mx - mn) * 0.9;

		int nextX = 0;
		int nextY = 0;
		TFixationsSet fixations;
		int nbIter = 0;
		int nbIter2 = 0;
		float salRequired = static_cast<float>(0.7 * smx);
		while(fixations.empty() && nbIter != 15 && nbIter2 < 10) {

			for(int ii = 0 ; ii < distanceMatrix.rows ; ++ii) {
				for(int jj = 0 ; jj < distanceMatrix.cols ; ++jj) {

					if(distanceMatrix.at<float>(ii,jj) > localThreshold) {

						bool keep = true;

						int nbPrevious = 0;
						for(std::list<cv::Point2i>::reverse_iterator it = fixationHistory.rbegin() ; it != fixationHistory.rend() && keep && nbPrevious < 20; ++it) {
							if(it->x == jj && it->y == ii) {
								keep = false;
							}
							++nbPrevious;
						}

						if(master_map.at<double>(ii,jj) < salRequired) keep = false;

						if(keep) {
							FixationOption f;
							f.x 			= jj;
							f.y 			= ii;
							f.value 		= static_cast<float>(master_map.at<double>(ii,jj));
							f.probability 	= distanceMatrix.at<float>(ii,jj);

							fixations.insert(f);
						}
					}
				}
			}

			if(fixations.empty()) {
				if(nbIter == 14) {
					salRequired = static_cast<float>(0.1 * smx); // relax constrain, if we cannot find a suitable position
					++nbIter2; 				 // make sure it will end.
					nbIter = 0;
				} else {
					salRequired = static_cast<float>(0.5 * smx);
				}
				++nbIter;
				getTransitionMatrix(lx, mm, distanceMatrix, position, 10.f * nbIter);
			}
		}

		int next_idx = rand() % 4;

		// if no fixation could be found, go to the most salient point nearby...
		if(fixations.empty()) {
			float localMax = 0;
			for(int ii = 0 ; ii < distanceMatrix.rows ; ++ii) {
				for(int jj = 0 ; jj < distanceMatrix.cols ; ++jj) {

					if(distanceMatrix.at<float>(ii,jj) > localMax) {
						if(!fixationHistory.empty() && fixationHistory.back().x == jj && fixationHistory.back().y == ii) continue;
						nextX = jj;
						nextY = ii;
						localMax = distanceMatrix.at<float>(ii,jj);
					}
				}
			}

			FixationOption f;
			f.x 			= nextX;
			f.y 			= nextY;
			f.value 		= static_cast<float>(master_map.at<double>(nextY,nextX));
			f.probability 	= distanceMatrix.at<float>(nextY,nextX);

			fixations.insert(f);

		}

		TFixationsSet::reverse_iterator it = fixations.rbegin();
		if(next_idx == 0) {
			nextX = it->x;
			nextY = it->y;
		} else {
			TFixationsSet::reverse_iterator prev_it = it;
			++it;

			for(int ii = 0 ; ii < next_idx && it != fixations.rend() ; ++ii) {
				prev_it = it;
				++it;
			}

			if(it != fixations.rend()) {
				nextX = it->x;
				nextY = it->y;
			} else {
				nextX = prev_it->x;
				nextY = prev_it->y;
			}
		}

		fixationHistory.push_back(cv::Point2i(nextX, nextY));

		position = nextX * distanceMatrix.rows + nextY;

#ifdef STUDY_FIX_PREDICTION

		cv::circle(saliency2, cv::Point2i(scaling * nextX, scaling * nextY), 6, cv::Scalar(1,1,0));

		if(i < static_cast<int>(groundTruthFixations.size()))
			cv::circle(saliency2, cv::Point2i(saliency2.cols * groundTruthFixations[i].x / static_cast<float>(imgBGR.cols), saliency2.rows * groundTruthFixations[i].y / static_cast<float>(imgBGR.rows)), 3, cv::Scalar(1,1,0));

		cv::imshow("saliency", saliency2);
		cv::waitKey();

		saliency2 = saliency.clone();

#endif

		out.at<float>(i, 2) = static_cast<float>(static_cast<int>(nextX * static_cast<float>(imgBGR.cols) / static_cast<float>(master_map.cols)));
		out.at<float>(i, 3) = static_cast<float>(static_cast<int>(nextY * static_cast<float>(imgBGR.cols) / static_cast<float>(master_map.cols)));
		out.at<float>(i, 1) = fixations.rbegin()->value;
		out.at<float>(i, 0) = static_cast<float>(i);
	}

	float sumProb = 0;
	for(int i = 1 ; i < out.rows ; ++i) {
		sumProb += out.at<float>(i, 1);
	}

	out.at<float>(0, 1) = 0;
	for(int i = 1 ; i < out.rows ; ++i) {
		out.at<float>(i, 1) = out.at<float>(i-1, 1) + Option::experimentLength * out.at<float>(i, 1) / sumProb;
	}

	return out;
}

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

void GBVS360::getRectilinearFrames(const cv::Mat &inputImage) {
	if(!m_Projection) {
		std::logic_error(std::string("Saliency360::getEquilinarFrames: The projection model was not allocated. Quit"));;
		return;
	}

	if(m_Projection->nreWidth == 0 || m_Projection->nreHeight == 0) {
		m_Projection->nreWidth = inputImage.cols;
		m_Projection->nreHeight = inputImage.rows;
	}

	float scaling_x = 2;
	float scaling_y = 1;

	int nb_projections_w = static_cast<int>(std::ceil(360.f / static_cast<float>(m_Projection->nrApper/scaling_x)));
	int nb_projections_h = static_cast<int>(std::ceil(180.f / static_cast<float>(m_Projection->nrApper/scaling_y)));

	// prepare all the projections: what needs to be done.
	for(int j = -nb_projections_h / 2 ; j <= nb_projections_h/2 ; ++j) { // for(int j = -nb_projections_h / 2 -1 ; j <= nb_projections_h/2 + 1; ++j) {
		for(int i = 0 ; i < nb_projections_w ; ++i) {

			m_ProjectedFrames.push_back(ProjectedFrame());
			ProjectedFrame &frame = m_ProjectedFrames.back();

			frame.rectilinearFrame = cv::Mat(m_Projection->nrrHeight, m_Projection->nrrWidth, CV_8UC3, inputImage.channels());
			if(frame.rectilinearFrame.empty()) {
				throw std::logic_error(std::string("Saliency360::getRectilinearFrames bad alloc..."));
				return ;
			}

			// frame.nrElev = static_cast<int>(j * m_Projection->nrApper/scaling_x);
			// frame.nrAzim = static_cast<int>(i * m_Projection->nrApper/scaling_y);
			frame.nrAzim = static_cast<int>(i * m_Projection->nrApper/scaling_x);
			frame.nrElev = static_cast<int>(j * m_Projection->nrApper/scaling_y);
			frame.taskDone = false;
		}
	}

	// Run `Option::threads` threads to do all the projections.
	boost::thread_group group;
	for(size_t i = 0 ; i < Option::threads ; ++i) {
		group.create_thread(boost::bind(&GBVS360::getRectilinearFramesJob, this, boost::ref(inputImage)));
	}
	group.join_all();

	// reset all task flags, to be ready for the next task
	for(std::list<ProjectedFrame>::iterator it = m_ProjectedFrames.begin() ; it != m_ProjectedFrames.end() ; ++it) {
		it->taskDone = false;
	}

}

void GBVS360::getRectilinearFramesJob(const cv::Mat &inputImage) {

	bool taskFound = true;

	while(taskFound) {
		taskFound = false;
		ProjectedFrame *projectedFrame = NULL;
		m_mutex.lock();
		int taskId = 0;
		for(std::list<ProjectedFrame>::iterator it = m_ProjectedFrames.begin() ; it != m_ProjectedFrames.end() ; ++it) {
			if(!it->taskDone) {
				taskFound = true;
				projectedFrame = &(*it);

				it->taskDone = true;		// marked the currently processed frame as estimated
				break;
			}
			++taskId;
		}

		m_mutex.unlock();

		// if there is still something to do, do the job
		if(taskFound) {
			m_Projection->equirectangularToRectilinear(inputImage, projectedFrame->rectilinearFrame, static_cast<float>(projectedFrame->nrAzim), static_cast<float>(projectedFrame->nrElev));

			if(hmdMode) {
				HMDSim simulator;
				cv::Mat result;
    			simulator.applyFilter(projectedFrame->rectilinearFrame, result);
    			result = 255*result;
    			result.convertTo(projectedFrame->rectilinearFrame, CV_8UC3);
			}
		}
	}
}

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

void GBVS360::getRectilinearFeatures	() {
	if(m_ProjectedFrames.empty()) return;

	int maxLevel = 0;
	for(size_t i = 0 ; i < levels.size() ; ++i) {
		if(levels[i] > maxLevel)
			maxLevel = levels[i];
	}

	if(maxLevel == 0) return;

	maxcomputelevel = maxLevel;
	int height = static_cast<int>(m_ProjectedFrames.front().rectilinearFrame.rows / (maxLevel*featureScaling));
	int width = static_cast<int>(m_ProjectedFrames.front().rectilinearFrame.cols  / (maxLevel*featureScaling));

	bool faceDetection = false;
	bool segmentation = false;
	bool linPerspective = false;
	std::string workChannels; // forward the requested channels to the workers. Unfortunately, Haarcascades classifiers does not seems to like multithreading. So, it will be needed to that in the main threads.
	for(size_t i = 0 ; i < channels.size() ; ++i) {
		if(channels[i] != 'F' &&  channels[i] != 'S'  &&  channels[i] != 'P')
			workChannels += channels[i];
		else {
			if(channels[i] == 'F')
				faceDetection = true;

			if(channels[i] =='S')
				segmentation = true;

			if(channels[i] == 'P')
				linPerspective = true;
		}

	}

	channels = workChannels + (linPerspective ? "P" : "") + (faceDetection ? "F" : "") + (segmentation ? "S" : "");	// make sure that F, S, P are in the last channel.

	for(size_t i = m_GBVSWorkers.size() ; i < Option::threads ; ++i) {		// instantiate all the workers;
		m_GBVSWorkers.push_back(boost::shared_ptr<GBVS>(new GBVS()));
		m_GBVSWorkers.back()->useCSF = false;
		m_GBVSWorkers.back()->initDone = true;		// the workers will not generate saliency maps, no need to initialize the graph
		m_GBVSWorkers.back()->maxcomputelevel = maxLevel; // we need to know what the deepest level. Normally this is estimated by initGBVS()
		m_GBVSWorkers.back()->makeGaborFilters();	// we need the Gabor filter
		m_GBVSWorkers.back()->salmapmaxsize_v.clear();
		m_GBVSWorkers.back()->salmapmaxsize_v.push_back(height);
		m_GBVSWorkers.back()->salmapmaxsize_v.push_back(width);
		m_GBVSWorkers.back()->channels = workChannels;

	}

	boost::thread_group group;
	for(int i = 0 ; i < static_cast<int>(m_GBVSWorkers.size()) ; ++i) {
		group.create_thread(boost::bind(&GBVS360::getRectilinearFeaturesJob, this, i));
	}
	group.join_all();

	// Face detection was requested, we now do it in the main thread.
	if(faceDetection) {
		getFaceDetectionFeature(height, width);
	}

}

void GBVS360::getRectilinearFeaturesJob(int workerID) {
	boost::shared_ptr<GBVS> &saliency = m_GBVSWorkers[workerID];

	// look for a frame which need to be processed
	bool taskFound = true;

	while(taskFound) {
		taskFound = false;
		ProjectedFrame *projectedFrame = NULL;
		m_mutex.lock();
		for(std::list<ProjectedFrame>::iterator it = m_ProjectedFrames.begin() ; it != m_ProjectedFrames.end() ; ++it) {
			if(!it->taskDone) {
				taskFound = true;
				projectedFrame = &(*it);

				it->taskDone = true;		// marked the currently processed frame as estimated
				break;
			}
		}

		m_mutex.unlock();

		// if there is still something to do, do the job
		if(taskFound) {
			saliency->computeFeatures(projectedFrame->rectilinearFrame);
			projectedFrame->features = saliency->features;
		}
	}
}

// Do the face detection in the mean thread as the haarcascades classifiers does not like multi-threading...
void GBVS360::getFaceDetectionFeature(int height, int width) {

	for(std::list<ProjectedFrame>::iterator it = m_ProjectedFrames.begin() ; it != m_ProjectedFrames.end() ; ++it) {
		it->taskDone = true;		// marked the currently processed frame as estimated

		ProjectedFrame *projectedFrame = &(*it);

		int channelNumber = 0;
		for(size_t i = 0 ; i < channels.size() ; ++i) {
			if(channels[i] == 'F') {
				break;
			}
			++channelNumber;
		}

		Feature f;
		f.description = "Face detector";
		f.weight = faceFeatureWeight;
		f.channel = channelNumber;

		cv::Mat featureMap = faceFeaturesDectection(projectedFrame->rectilinearFrame);

		if(!featureMap.empty()) {
			FeatureMap fm;
			fm.type  = 0;
			fm.level = 0;
			fm.channel = static_cast<int>(channels.size())-1;

			cv::resize(featureMap, fm.map, cv::Size(width, height), 0, 0, cv::INTER_AREA);
			f.maps.push_back(fm);

			projectedFrame->features.push_back(f);
		}
	}
}

void GBVS360::getSegmentationFeature(const cv::Mat &inputImage, Feature &segFeature) {
	cv::Mat input = inputImage.clone();
	input.convertTo(input, CV_64FC3);
	input /= 255;
	segFeature = segmentationFeature(input);

	int featureIndex = 0;
	for(size_t i = 0 ; i < channels.size() ; ++i) {
		if(channels[i] == 'S') {
			featureIndex = static_cast<int>(i);
			break;
		}
	}

	segFeature.channel = featureIndex;
	for(std::list<FeatureMap>::iterator it = segFeature.maps.begin() ; it !=  segFeature.maps.end() ; ++it) {
		it->channel = featureIndex;
	}
}

void GBVS360::getLinPerFeature(const cv::Mat &inputImage, Feature &linPerFeature) {
	cv::Mat image;
	cv::resize(inputImage, image, cv::Size(1200, static_cast<int>((1000.f/inputImage.cols) * inputImage.rows)));

	std::vector<double> reliability(4);
    std::vector<cv::Mat> outputs(4);
    for(size_t i = 0 ; i < outputs.size() ; ++i) {
		outputs[i] = vanishingLineFeatureMap(image, &reliability[i]);
    }

    for(size_t i = 1 ; i < outputs.size() ; ++i) {
        outputs[0] += outputs[i];
        reliability[0] += reliability[i];
    }
    outputs[0] /= static_cast<double>(outputs.size());
    reliability[0] /= static_cast<double>(outputs.size());

	FeatureMap fm;
	fm.type  = 0;
	fm.level = 0;
	for(size_t i = 0 ; i < channels.size() ; ++i) {
		if(channels[i] == 'P') {
			fm.channel = static_cast<int>(i);
		}
	}

	if(reliability[0] < 0.0015) {	// 0.0038 if the feature map does not contains much information, drop it.
		fm.map = cv::Mat(cv::Size(salmapmaxsize_v[1], salmapmaxsize_v[0]), CV_64FC1, cv::Scalar(0.f));
	} else {
		cv::resize(outputs[0], fm.map, cv::Size(salmapmaxsize_v[1], salmapmaxsize_v[0]), 0, 0, cv::INTER_AREA);
	}

	linPerFeature.maps.push_back(fm);
	linPerFeature.description = "Linear perspective";
	if(reliability[0] >= 0.0038)
		linPerFeature.weight = std::min(100.f, std::max(0.f, static_cast<float>(-2.824f + 3.105f * exp(662.373f * reliability[0])))) / 10.f; //30 / 70
	else
		linPerFeature.weight = std::min(100.f, std::max(0.f, static_cast<float>(-3.276f + 2.936f * exp(689.485f * reliability[0])))) / 140.f; //100

	linPerFeature.channel = fm.channel;
}

void GBVS360::getEquirectangularFeatures() {

	if(m_ProjectedFrames.empty()) { std::cout << "[I] No projected frames... " << std::endl; return; } 					// If there are no frames available, stop.
	ProjectedFrame &frame = *m_ProjectedFrames.begin();
	if(frame.features.empty()) { std::cout << "[I] No features" << std::endl; return; };						// If there are no features computed, stop.

	int mxType = 0;
	int mxLevel = 0;
	int mxChannel = 0;
	// allocate enough memory for all features in equirectangular domain.
	for(std::list<Feature>::iterator it = frame.features.begin() ; it != frame.features.end() ; ++it) {
		features.push_back(Feature());
		features.back().weight 		= it->weight;
		features.back().channel 	= it->channel;
		features.back().description = it->description;

		for(std::list< FeatureMap >::iterator mapIt = it->maps.begin() ; mapIt != it->maps.end() ; ++mapIt) {
			features.back().maps.push_back(FeatureMap());
			features.back().maps.back().type 	= mapIt->type;
			features.back().maps.back().level 	= mapIt->level;
			features.back().maps.back().channel = mapIt->channel;

			// the interpolation in the projection function needs an image in with a power of 2.
			int rows = 4 * (static_cast<int>(m_EquirectangularFrameSize.height / (maxcomputelevel*featureScaling)) / 4); // 0
			int cols = 4 * (static_cast<int>(m_EquirectangularFrameSize.width / (maxcomputelevel*featureScaling)) / 4); // 1

			features.back().maps.back().map = cv::Mat(rows, cols, CV_64FC1, cv::Scalar(0));

			mxType = std::max(mxType, mapIt->type);
			mxLevel = std::max(mxLevel, mapIt->level);
			mxChannel = std::max(mxChannel, mapIt->channel);
		}
	}

	// add flags to know if a feature was projected to equirectangular domain.
	// order is : featureDone[CHANNEL][LEVEL][TYPE]
	featureDone.clear();
	++mxChannel; ++mxLevel; ++mxType;
	featureDone.resize(mxChannel);
	for(int i = 0 ; i < mxChannel ; ++i) {
		featureDone[i].resize(mxLevel);
		for(int j = 0 ; j < mxLevel ; ++j) {
			featureDone[i][j].resize(mxType, false);
		}
	}

	// Run `Option::threads` threads to do all the projections.
	boost::thread_group group;
	for(size_t i = 0 ; i < Option::threads ; ++i) {
		group.create_thread(boost::bind(&GBVS360::getEquirectangularFeaturesJob, this));
	}

	// if the feature S is requested, we compute it directly in equirectangular domain.
	bool doSegmentation = false;
	Feature segFeature;
	for(size_t i = 0 ; i < channels.size() && !doSegmentation ; ++i) {
		if(channels[i] == 'S') {
			group.create_thread(boost::bind(&GBVS360::getSegmentationFeature, this, boost::ref(m_InputImage), boost::ref(segFeature)));
			doSegmentation = true;
		}
	}

	// if the feature S is requested, we compute it directly in equirectangular domain.
	bool doPerspective = false;
	Feature linPerFeature;
	for(size_t i = 0 ; i < channels.size() && !doPerspective ; ++i) {
		if(channels[i] == 'P') {
			//getLinPerFeature(m_InputImage, linPerFeature);
			group.create_thread(boost::bind(&GBVS360::getLinPerFeature, this, boost::ref(m_InputImage), boost::ref(linPerFeature)));
			doPerspective = true;
		}
	}

	group.join_all();

	// add the segmentation feature maps which was estimated in parallel.
	if(doSegmentation) {
		features.push_back(segFeature);
	}

	if(doPerspective) {
		double mx, mn;
		cv::minMaxLoc(linPerFeature.maps.back().map, &mn, &mx);
		if(mx > 0.1) {
			features.push_back(linPerFeature);
			// allmaps.push_back(linPerFeature.maps.front());
		}
	}

}

void GBVS360::getEquirectangularFeaturesJob() {

	bool taskFound = true;
	while(taskFound) {
		taskFound = false;

		// algo v1, easy, without packing of multiple features with the same resolution to save computational costs...
		if(m_ProjectedFrames.empty()) break; 					// If there are no frames available, stop.
		ProjectedFrame &frame = *m_ProjectedFrames.begin();
		if(frame.features.empty()) break;						// If there are no features computed, stop.

		int channel = 0;
		int level = 0;
		int type = 0;

		// ----------------------------------------------------------------------------------
		// identify what task need to be done

		m_mutex.lock();

		// iterate each feature channel
		for(std::list<Feature>::iterator it = frame.features.begin() ; it != frame.features.end() && !taskFound ; ++it) {
			// iterate each feature map
			for(std::list< FeatureMap >::iterator mapIt = it->maps.begin() ; mapIt != it->maps.end()  && !taskFound ; ++mapIt) {
				if(!featureDone[mapIt->channel][mapIt->level][mapIt->type]) {

					// we found one feature map which needs to be back projected.
					channel = mapIt->channel;
					level = mapIt->level;
					type = mapIt->type;

					// we mark it as `done`, e.g. in progress...
					featureDone[mapIt->channel][mapIt->level][mapIt->type] = true;

					// a task was found.
					taskFound = true;
				}
			}
		}

		m_mutex.unlock();

		// if nothing to do, then close the thread.
		if(!taskFound) {
			return;
		}

		// ----------------------------------------------------------------------------------
		// back-project the identified feature map.

		// Find the feature map between the equirectilinear maps
		for(std::list<Feature>::iterator featureItEquirect = features.begin() ; featureItEquirect != features.end() ; ++featureItEquirect) {
			if(featureItEquirect->channel != channel) continue; // if we are not on the right channel, no need to check the maps

			// iterate each feature map
			for(std::list< FeatureMap >::iterator mapItEquirectilinear = featureItEquirect->maps.begin() ; mapItEquirectilinear != featureItEquirect->maps.end() ; ++mapItEquirectilinear) {
				if(mapItEquirectilinear->level == level && mapItEquirectilinear->type == type && mapItEquirectilinear->channel == channel) {

					cv::Mat nbProj(mapItEquirectilinear->map.rows, mapItEquirectilinear->map.cols, CV_8UC1, cv::Scalar(0));

					for(std::list< ProjectedFrame >::iterator projIt = m_ProjectedFrames.begin() ; projIt != m_ProjectedFrames.end() ; ++projIt) {

						// Find the feature map between the equilinear maps
						for(std::list<Feature>::iterator featureIt = projIt->features.begin() ; featureIt != projIt->features.end() ; ++featureIt) {

							if(featureIt->channel != channel) continue; // if we are not on the right channel, no need to check the maps

							// iterate each feature map
							for(std::list< FeatureMap >::iterator mapItEquilinear = featureIt->maps.begin() ; mapItEquilinear != featureIt->maps.end() ; ++mapItEquilinear) {

								if((mapItEquilinear->level == level && mapItEquilinear->type == type && mapItEquilinear->channel == channel)) {

									cv::Mat tempEquirect(mapItEquirectilinear->map.rows, mapItEquirectilinear->map.cols, CV_32FC3, cv::Scalar(0.f,0.f,0.f));
									cv::Mat floatFeature(mapItEquilinear->map.rows, mapItEquilinear->map.cols, CV_32FC3, cv::Scalar(0.f,0.f,0.f));

									for(int i = 0 ; i < mapItEquilinear->map.rows ; ++i) {
										for(int j = 0 ; j < mapItEquilinear->map.cols ; ++j) {
											cv::Point3_<float> &dst = floatFeature.at< cv::Point3_<float> >(i,j);
											dst.x = static_cast<float>(mapItEquilinear->map.at<double>(i,j));
											dst.y = 1;
											dst.z = 0;
										}
									}

									m_Projection->rectilinearToEquirectangularFC3(floatFeature, tempEquirect, static_cast<float>(projIt->nrAzim), static_cast<float>(projIt->nrElev));

									for(int i = 0 ; i < tempEquirect.rows ; ++i) {
										for(int j = 0 ; j < tempEquirect.cols ; ++j) {
											cv::Point3_<float> &src = tempEquirect.at< cv::Point3_<float> >(i,j);

											// if that is not part of the feature, skip
											if(src.y < 0.999f) continue;

											double v = mapItEquirectilinear->map.at<double>(i,j);

											v += src.x;

											++nbProj.at<unsigned char>(i,j);

											mapItEquirectilinear->map.at<double>(i,j) = v;

										}
									}
								}
							}
						}

					} // end loop projIt

					for(int i = 0 ; i < mapItEquirectilinear->map.rows ; ++i) {
						for(int j = 0 ; j < mapItEquirectilinear->map.cols ; ++j) {
							mapItEquirectilinear->map.at<double>(i,j) /= nbProj.at<unsigned char>(i,j);
						}
					}

					cv::Mat tmp;
					cv::resize(mapItEquirectilinear->map, tmp, cv::Size(salmapmaxsize_v[1], salmapmaxsize_v[0]), 0, 0, cv::INTER_AREA);
					mapItEquirectilinear->map = tmp;
				}
			}
		}

	} // end while task

}

// ----------------------------------------------------------------------------------------------------------------------------------------------------
// redefine GBVS functions

cv::Mat GBVS360::simpledistance	(const std::pair<int, int>& dim, int ) const {

	return Option::distScaling * GBVS::simpledistance(dim, 0);

	int N = dim.first*dim.second;
	cv::Mat d(N, N, CV_64FC1, cv::Scalar(0.f));

	double l0 = static_cast<float>(dim.second)/2;
	double p0 = static_cast<float>(dim.first)/2;

	#define idx(mi,mj) ((mj)*dim.first+(mi))

	#ifndef PI
	#define PI 3.14159265358979116f
	#endif

	for(int i = 0 ; i < dim.second ; ++i) {
		for(int j = 0 ; j < dim.first ; ++j) {
			for(int ii = 0 ; ii < dim.second ; ++ii) {
				for(int jj = 0 ; jj < dim.first ; ++jj) {

					// replace the distance by the haversine distance to account for the non-linearity of the grid in equirectangular domain.
					if(d.at<double>(idx(j,i), idx(jj,ii)) == 0) {

						double haversine = 0;

						// TO CHECK:
						// shall this be " - p0 " & "/ dim.first" as j loop between [0-dim.first] ?
						// I think that mistake added an equatorial prior which is a positive thing...
                        double phy1   = PI*(j + .5  - p0) / static_cast<double>(dim.first);
                        double phy2   = PI*(jj + .5 - p0) / static_cast<double>(dim.first);
                        double lambda1 =  PI*(i + .5 - l0) / static_cast<double>(dim.second)/2;
                        double lambda2 =  PI*(ii + .5 - l0) / static_cast<double>(dim.second)/2;

                        double sn1 = std::sin((phy2-phy1)/2);
                        double sn2 = std::sin((lambda2-lambda1)/2);

                        haversine = sqrt(sn1*sn1 + std::cos(phy1)*std::cos(phy2)*sn2*sn2);
                        if(haversine > 1.0)  haversine = 1.0;	// this can happens due to rounding error...
                        if(haversine < -1.0) haversine = -1.0;

                        // haversine = m_Projection->nrrWidth*dim.first*std::asin(haversine); //dim.first*
						haversine = Option::distScaling * dim.first*std::asin(haversine); //dim.first*
						// haversine = dim.first*std::asin(haversine);

						// haversine = dim.first * 2 * std::atan2(std::sqrt(haversine), std::sqrt(1-haversine));

						d.at<double>(idx(j,i), idx(jj,ii)) = haversine;
						d.at<double>(idx(jj,ii), idx(j,i)) = haversine;
					}
				}
			}
		}
	}

	#undef idx

	return d;
}

void GBVS360::attenuateBordersGBVS(cv::Mat &, int ) const {
	// do nothing -- remove central prior

	// if(size == 4) return;

	// if(size == 13) GBVS::attenuateBordersGBVS(image, 5);
}

const cv::Mat * GBVS360::findMap(char channel, int level, int type) const {
	int channelId = -1;
	for(int i = 0 ; i < static_cast<int>(channels.size()) ; ++i) {
		if(channels[i] == channel) {
			channelId = i;
			break;
		}
	}

	if(channelId == -1) return NULL;

	for(std::list<FeatureMap>::const_iterator it = allmaps.begin() ; it != allmaps.end() ; ++it) {
		if(it->channel == channelId && it->type == type && it->level == level)
			return &it->map;
	}

	return NULL;
}